├── .gitignore ├── .huskyrc ├── .prettierrc ├── LICENSE ├── README.md ├── _redirects ├── commitlint.config.js ├── package.json ├── public ├── CNAME ├── Montserrat-Bold.ttf ├── Montserrat-Medium.ttf ├── Montserrat-SemiBold.ttf ├── favicon.png ├── index.html ├── manifest.json └── robots.txt ├── server.js ├── src ├── actions │ ├── i18n │ │ └── index.js │ ├── loopring │ │ └── index.js │ └── web3 │ │ └── index.js ├── components │ ├── button │ │ └── index.jsx │ ├── chip │ │ ├── index.jsx │ │ └── styled.js │ ├── drawer │ │ ├── index.jsx │ │ ├── sections │ │ │ ├── exchange-info │ │ │ │ └── index.jsx │ │ │ ├── lrc │ │ │ │ └── index.jsx │ │ │ ├── settings │ │ │ │ ├── index.jsx │ │ │ │ └── styled.js │ │ │ ├── support │ │ │ │ └── index.jsx │ │ │ └── technical-resources │ │ │ │ └── index.jsx │ │ └── styled.js │ ├── full-screen-overlay │ │ └── index.jsx │ ├── invalid-chain-id │ │ ├── index.jsx │ │ └── styled.js │ ├── layout │ │ ├── footer │ │ │ ├── index.jsx │ │ │ └── styled.js │ │ ├── index.jsx │ │ ├── styled.js │ │ └── toolbar │ │ │ ├── index.jsx │ │ │ └── styled.js │ ├── spinner │ │ └── index.jsx │ ├── switch │ │ └── index.jsx │ ├── token-icon │ │ ├── index.jsx │ │ └── styled.js │ ├── token-modal │ │ ├── index.jsx │ │ └── styled.js │ ├── token-select │ │ ├── index.jsx │ │ └── styled.js │ ├── token-specifier │ │ ├── index.jsx │ │ └── styled.js │ └── undecorated-link │ │ └── index.jsx ├── env │ └── index.js ├── i18n │ └── messages │ │ ├── en.json │ │ └── it.json ├── images │ ├── keep.png │ ├── logo.png │ ├── logo.svg │ ├── nest.png │ └── pnetwork.png ├── index.js ├── lightcone │ ├── api │ │ ├── CommissionRewardAPI.js │ │ ├── LightconeAPI.js │ │ ├── LiquidityMiningAPI.js │ │ ├── localStorgeAPI.js │ │ └── v1 │ │ │ ├── allowances │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── balances │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── depth │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── ethBalance │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── ethnonce │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── fee-rates │ │ │ └── index.js │ │ │ ├── marketinfo │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── onchainwithdrawal │ │ │ ├── get.js │ │ │ ├── index.js │ │ │ └── post.js │ │ │ ├── orders │ │ │ ├── delete.js │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── price │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── recommendedGasPrice │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── tokenBalance │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── tokeninfo │ │ │ ├── get.js │ │ │ └── index.js │ │ │ ├── transfer │ │ │ ├── get.js │ │ │ ├── index.js │ │ │ └── post.js │ │ │ └── userTrades │ │ │ ├── get.js │ │ │ └── index.js │ ├── common │ │ ├── formatter.js │ │ ├── index.js │ │ ├── request.js │ │ ├── schemas.js │ │ ├── utils.js │ │ └── validator.js │ ├── config │ │ ├── abis │ │ │ ├── contractWallet.json │ │ │ ├── erc20.json │ │ │ └── exchange.json │ │ ├── config.json │ │ └── index.js │ ├── ethereum │ │ ├── contracts │ │ │ ├── AbiFunction.js │ │ │ ├── Contract.js │ │ │ ├── Contracts.js │ │ │ └── index.js │ │ └── metaMask.js │ ├── sign │ │ ├── babyjub.js │ │ ├── bitstream.js │ │ ├── constants.js │ │ ├── eddsa.js │ │ ├── exchange.js │ │ ├── float.js │ │ └── poseidon.js │ └── wallet │ │ ├── index.js │ │ └── wallet.js ├── reducers │ ├── i18n │ │ └── index.js │ ├── index.js │ ├── loopring │ │ └── index.js │ └── web3 │ │ └── index.js ├── serviceWorker.js ├── utils │ └── index.js └── views │ ├── app │ ├── index.jsx │ └── styled.js │ └── swapper │ ├── index.jsx │ └── styled.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env.* 26 | .env* 27 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "husky": { 3 | "hooks": { 4 | "commit-msg": "lint:commit-message" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "singleQuote": false, 4 | "tabWidth": 4 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Loopring Swap 2 | 3 | An effort to implement order-book-transparent L2 token swaps powered by the Loopring protocol. 4 | Fast, cheap and painless: join the fast lane, today. 5 | 6 | ## Environment 7 | 8 | Before you can start hacking, some environment variables setting is going to be needed. Particularly: 9 | 10 | - `CHAIN_ID`: the chain id to which the app refers to. It is used to determine the Loopring'a API endpoint and the general app's environment (the only supported values are `1` for mainnet and `5` for Goerli). 11 | - `INFURA_URL`: a valid Infura Ethereum provider URL 12 | - `DEX_SMART_CONTRACT_ADDRESS`: the address of the Loopring DEX smart contract in the currently active network (defined by `CHAIN_ID`). It is required, but the value does not need to be precise when developing. It is used in the drawer menu to direct the user to the correct contract on Etherscan when clicking on the "DEX smart contract" item. 13 | 14 | The app uses `dotenv` to load environment variables during local development, so using customized variables is as simple as creating a `.env` file in the project's root and writing them there. 15 | 16 | This being a React app, the environment variables need to be prefixed by `REACT_APP` in order to be picked up. 17 | 18 | ## Development 19 | 20 | To start up a local instance of the app, just use the command `yarn start`. 21 | 22 | **Stay tuned for official beta v1 release, a stable v1 and a possible v2.** 23 | 24 | ## Screenshots 25 | 26 | Light theme | Dark theme 27 | -|- 28 | ![screenshot-1](https://siasky.net/rACmOAUfTDiCrkUZ5lvPKqgriwos6nqfkSLnmw6xBnzEEg) | ![screenshot-2](https://siasky.net/zAD8Gs94XcvsKGbzBk2bcLvhCbEBQME6u48nXXSKRghqvQ) 29 | ![screenshot-3](https://siasky.net/3AFD83ZA-fTj8mU63ZuAtdYOYhuzfn1cIO54f-moJ900fg) | ![screenshot-4](https://siasky.net/3AEYRumBOmbr7eEXTl8AbhISz1Mp1kWgtDqkre8_0eY9iw) 30 | ![screenshot-5](https://siasky.net/3ADTvVqM4AkCtCuZ2Tldx32e0drjx_k4fce81aBXH0xeEg) | ![screenshot-6](https://siasky.net/nAAzUNwbUSUesj2qAOQZhA_Zaqzw2Zo49DKerdAroaOvsQ) 31 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ["@commitlint/config-conventional"] }; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopring-swap", 3 | "version": "1.0.0-beta.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^1.2.30", 7 | "@fortawesome/free-brands-svg-icons": "^5.14.0", 8 | "@fortawesome/free-solid-svg-icons": "^5.14.0", 9 | "@fortawesome/react-fontawesome": "^0.1.11", 10 | "@mondora/env": "^1.3.0", 11 | "@myetherwallet/mewconnect-web-client": "^2.1.1-RC.4.6", 12 | "@testing-library/jest-dom": "^4.2.4", 13 | "@testing-library/react": "^9.3.2", 14 | "@testing-library/user-event": "^7.1.2", 15 | "@walletconnect/web3-provider": "^1.2.2", 16 | "async-validator": "^3.4.0", 17 | "authereum": "^0.0.4-beta.193", 18 | "axios": "^0.19.2", 19 | "bignumber.js": "^9.0.0", 20 | "blake-hash": "^2.0.0", 21 | "blake2b": "^2.1.3", 22 | "crypto-js": "^4.0.0", 23 | "ethereumjs-abi": "^0.6.8", 24 | "helmet": "^4.1.1", 25 | "moment": "^2.27.0", 26 | "react": "^16.13.1", 27 | "react-dom": "^16.13.1", 28 | "react-intl": "^5.4.7", 29 | "react-number-format": "^4.4.1", 30 | "react-redux": "^7.2.1", 31 | "react-scripts": "3.4.3", 32 | "react-switch": "^5.0.1", 33 | "react-toastify": "^6.0.8", 34 | "redux": "^4.0.5", 35 | "redux-thunk": "^2.3.0", 36 | "reflexbox": "^4.0.6", 37 | "snarkjs": "0.1.20", 38 | "styled-components": "^5.2.0", 39 | "use-debounce": "^3.4.3", 40 | "web3": "^1.2.11", 41 | "web3-utils": "^1.2.11", 42 | "web3modal": "^1.9.0" 43 | }, 44 | "scripts": { 45 | "analyze": "source-map-explorer 'build/static/js/*.js'", 46 | "start": "react-scripts start", 47 | "build": "react-scripts build && copy _redirects ./build", 48 | "test": "react-scripts test", 49 | "eject": "react-scripts eject", 50 | "lint:commit-message": "commitlint -e", 51 | "predeploy": "yarn build", 52 | "deploy": "gh-pages -d build" 53 | }, 54 | "eslintConfig": { 55 | "extends": "react-app" 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | }, 69 | "devDependencies": { 70 | "@commitlint/cli": "^9.1.1", 71 | "@commitlint/config-conventional": "^9.1.1", 72 | "copy": "^0.3.2", 73 | "dotenv": "^8.2.0", 74 | "gh-pages": "^2.1.1", 75 | "husky": "^4.2.5" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | swap.loopring.io -------------------------------------------------------------------------------- /public/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Loopring/loopring-swap/b55803ec0105a7f22abcbcdf04a77276e03e08b8/public/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /public/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Loopring/loopring-swap/b55803ec0105a7f22abcbcdf04a77276e03e08b8/public/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /public/Montserrat-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Loopring/loopring-swap/b55803ec0105a7f22abcbcdf04a77276e03e08b8/public/Montserrat-SemiBold.ttf -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Loopring/loopring-swap/b55803ec0105a7f22abcbcdf04a77276e03e08b8/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 27 | Loopring Swap 28 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | { 4 | "src": "favicon.png", 5 | "sizes": "32x32", 6 | "type": "image/png" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // [START app] 2 | const express = require('express'); 3 | const helmet = require('helmet'); 4 | const bodyParser = require('body-parser'); 5 | const path = require('path'); 6 | 7 | const os = require('os'); 8 | const cluster = require('cluster'); 9 | const clusterWorkerSize = Math.floor(os.cpus().length / 2); 10 | 11 | // Listen to the App Engine-specified port, or 8080 otherwise 12 | const PORT = process.env.PORT || 6999; 13 | let app; 14 | 15 | if (clusterWorkerSize > 1) { 16 | if (cluster.isMaster) { 17 | for (let i = 0; i < clusterWorkerSize; i++) { 18 | cluster.fork(); 19 | } 20 | 21 | cluster.on('exit', function (worker) { 22 | console.log('Worker', worker.id, ' has exitted.'); 23 | }); 24 | } else { 25 | app = express(); 26 | app.use( 27 | helmet.contentSecurityPolicy({ 28 | directives: { 29 | 'default-src': [ 30 | "'self'", 31 | 'data:', 32 | "'unsafe-inline'", 33 | "'unsafe-eval'", 34 | 'uat.loopring.io', 35 | 'wss://ws.uat.loopring.io', 36 | 'api.loopring.io', 37 | 'wss://ws.loopring.io', 38 | 'wss://bridge.walletconnect.org', 39 | 'wss://mainnet.infura.io', 40 | '*.infura.io', 41 | '*.bandchain.org', 42 | '*.authereum.com', 43 | 'wss://connect.mewapi.io', 44 | 'wss://connect2.mewapi.io', 45 | 'fonts.googleapis.com', 46 | 'fonts.gstatic.com', 47 | 'blob:', 48 | ], 49 | 'img-src': ["'self'", 'data:', 'cnzz.mmstat.com', '*.cnzz.com', 'loopring.io'], 50 | 'script-src': [ 51 | "'self'", 52 | "'unsafe-inline'", 53 | "'unsafe-eval'", 54 | '*.cnzz.com', 55 | 'uat.loopring.io', 56 | 'ws.uat.loopring.io', 57 | 'api.loopring.io', 58 | 'ws.loopring.io', 59 | 'connect.mewapi.io', 60 | 'blob:', 61 | ], 62 | }, 63 | }) 64 | ); 65 | app.use(helmet.dnsPrefetchControl()); 66 | app.use(helmet.expectCt()); 67 | app.use(helmet.frameguard()); 68 | app.use(helmet.hidePoweredBy()); 69 | app.use(helmet.hsts()); 70 | app.use(helmet.ieNoOpen()); 71 | app.use(helmet.noSniff()); 72 | app.use(helmet.permittedCrossDomainPolicies()); 73 | app.use(helmet.referrerPolicy()); 74 | app.use(helmet.xssFilter()); 75 | 76 | // One day: 86400 77 | // One hour: 3600 78 | const CACHE_CONTROL = 'public, max-age=3600, s-maxage=3600'; 79 | var DIST_DIR = path.join(__dirname, 'build'); 80 | 81 | app.use(bodyParser.json()); 82 | app.use(bodyParser.urlencoded({ extended: true })); 83 | 84 | app.get('/', (req, res) => { 85 | res.set('Cache-Control', CACHE_CONTROL); 86 | res.sendFile(path.join(DIST_DIR, '/index.html')); 87 | }); 88 | 89 | app.get('/manifest.json', (req, res) => { 90 | res.sendFile(path.join(DIST_DIR, '/manifest.json')); 91 | }); 92 | 93 | app.get('/favicon.png', (req, res) => { 94 | res.sendFile(path.join(DIST_DIR, '/favicon.png')); 95 | }); 96 | 97 | app.get('/favicon.svg', (req, res) => { 98 | res.sendFile(path.join(DIST_DIR, '/favicon.svg')); 99 | }); 100 | 101 | app.use('/', express.static(DIST_DIR, { maxAge: '1d' })); 102 | 103 | app.get('*', (req, res) => { 104 | res.set('Cache-Control', CACHE_CONTROL); 105 | res.sendFile(path.join(DIST_DIR, '/index.html')); 106 | }); 107 | 108 | app.listen(PORT, function () { 109 | console.log( 110 | `Express server listening on port ${PORT} and worker ${process.pid}` 111 | ); 112 | }); 113 | } 114 | } 115 | 116 | // [END app] 117 | 118 | module.exports = app; 119 | -------------------------------------------------------------------------------- /src/actions/i18n/index.js: -------------------------------------------------------------------------------- 1 | export const SWITCH_LANGUAGE = "CHANGE_LANGUAGE"; 2 | 3 | export const switchLanguage = (language) => ({ 4 | type: SWITCH_LANGUAGE, 5 | language, 6 | }); 7 | -------------------------------------------------------------------------------- /src/actions/web3/index.js: -------------------------------------------------------------------------------- 1 | import { getWeb3Modal } from "../../views/app"; 2 | import Web3 from "web3"; 3 | import { logout } from "../loopring"; 4 | 5 | export const INITIALIZE_WEB3_SUCCESS = "INITIALIZE_WEB3_SUCCESS"; 6 | export const WEB3_ACCOUNT_CHANGED = "WEB3_ACCOUNT_CHANGED"; 7 | export const CHAIN_ID_CHANGED = "CHAIN_ID_CHANGED"; 8 | 9 | export const initializeWeb3 = () => async (dispatch) => { 10 | try { 11 | const web3Modal = getWeb3Modal(); 12 | const provider = await web3Modal.connect(); 13 | provider.autoRefreshOnNetworkChange = false; 14 | provider.on("chainChanged", (hexChainId) => { 15 | dispatch({ 16 | type: CHAIN_ID_CHANGED, 17 | chainId: parseInt(hexChainId, 16), 18 | }); 19 | dispatch(logout()); 20 | }); 21 | provider.on("accountsChanged", () => { 22 | dispatch(logout()); 23 | }); 24 | const web3 = new Web3(provider); 25 | dispatch({ 26 | type: CHAIN_ID_CHANGED, 27 | chainId: await web3.eth.getChainId(), 28 | }); 29 | const accounts = await web3.eth.getAccounts(); 30 | dispatch({ 31 | type: INITIALIZE_WEB3_SUCCESS, 32 | web3, 33 | selectedAccount: accounts[0], 34 | }); 35 | } catch (error) { 36 | console.error("error initializing web3", error); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/button/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled, { css } from "styled-components"; 4 | import { Flex, Box } from "reflexbox"; 5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 6 | import { Spinner } from "../spinner"; 7 | 8 | const sizeMap = { 9 | small: { 10 | height: 32, 11 | fontSize: 12, 12 | }, 13 | medium: { 14 | height: 36, 15 | fontSize: 16, 16 | }, 17 | large: { 18 | height: 48, 19 | fontSize: 16, 20 | }, 21 | }; 22 | 23 | const commonsStyles = css` 24 | display: flex; 25 | white-space: nowrap; 26 | justify-content: center; 27 | align-items: center; 28 | height: ${(props) => sizeMap[props.size].height}px; 29 | padding: 0 20px; 30 | font-size: ${(props) => sizeMap[props.size].fontSize}px; 31 | font-family: Montserrat, sans-serif; 32 | background: ${(props) => 33 | props.disabled ? props.theme.disabled : props.theme.primary}; 34 | color: ${(props) => 35 | props.disabled ? props.theme.textDisabled : props.theme.textButton}; 36 | border: none; 37 | border-radius: 12px; 38 | font-weight: 600; 39 | transition: all 0.3s ease; 40 | outline: none; 41 | cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; 42 | text-decoration: none; 43 | `; 44 | 45 | const StyledButton = styled.button` 46 | ${commonsStyles} 47 | `; 48 | 49 | export const Button = ({ 50 | children, 51 | faIcon, 52 | size, 53 | loading, 54 | disabled, 55 | ...rest 56 | }) => ( 57 | 58 | 59 | {loading && !disabled ? ( 60 | 66 | 70 | 71 | ) : ( 72 | <> 73 | {faIcon && ( 74 | 75 | 76 | 77 | )} 78 | {children} 79 | 80 | )} 81 | 82 | 83 | ); 84 | 85 | Button.propTypes = { 86 | faIcon: PropTypes.object, 87 | size: PropTypes.oneOf(["small", "medium", "large"]), 88 | loading: PropTypes.bool, 89 | disabled: PropTypes.bool, 90 | }; 91 | 92 | Button.defaultProps = { 93 | size: "medium", 94 | }; 95 | -------------------------------------------------------------------------------- /src/components/chip/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { RootFlex } from "./styled"; 4 | 5 | export const Chip = ({ selected, onClick, children }) => ( 6 | 7 | {children} 8 | 9 | ); 10 | 11 | Chip.propTypes = { 12 | children: PropTypes.node.isRequired, 13 | selected: PropTypes.bool.isRequired, 14 | onClick: PropTypes.func.isRequired, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/chip/styled.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Flex } from "reflexbox"; 3 | 4 | export const RootFlex = styled(Flex)` 5 | border: solid 1px ${(props) => props.theme.border}; 6 | border-radius: 12px; 7 | padding: 2px 8px; 8 | font-size: 12px; 9 | background: ${(props) => 10 | props.selected ? props.theme.primary : props.theme.foreground}; 11 | color: ${(props) => 12 | props.selected ? props.theme.textInverted : props.theme.text}; 13 | overflow: hidden; 14 | text-overflow: ellipsis; 15 | white-space: nowrap; 16 | transition: background 0.3s ease, border 0.3s ease, color 0.3s ease; 17 | cursor: pointer; 18 | `; 19 | -------------------------------------------------------------------------------- /src/components/drawer/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useLayoutEffect } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Box } from "reflexbox"; 4 | import { 5 | faPlug, 6 | faLock, 7 | faLockOpen, 8 | faTimes, 9 | faUserPlus, 10 | } from "@fortawesome/free-solid-svg-icons"; 11 | import { selectedTheme } from "../../views/app"; 12 | import { FormattedMessage } from "react-intl"; 13 | import { 14 | RootFlex, 15 | HeaderFlex, 16 | Icon, 17 | EllipsizedBox, 18 | CloseIcon, 19 | SummaryMessage, 20 | } from "./styled"; 21 | import { Lrc } from "./sections/lrc"; 22 | import { Support } from "./sections/support"; 23 | import { TechnicalResources } from "./sections/technical-resources"; 24 | import { ExchangeInfo } from "./sections/exchange-info"; 25 | import { Settings } from "./sections/settings"; 26 | import { Button } from "../button"; 27 | import { useCallback } from "react"; 28 | import { getShortenedEthereumAddress } from "../../utils"; 29 | 30 | export const Drawer = ({ 31 | open, 32 | onClose, 33 | onConnectWallet, 34 | selectedWeb3Account, 35 | onLogin, 36 | onLogout, 37 | loggedIn, 38 | darkTheme, 39 | onDarkThemeChange, 40 | selectedLanguage, 41 | onSelectedLanguageChange, 42 | }) => { 43 | const container = useRef(null); 44 | 45 | const [icon, setIcon] = useState(faLock); 46 | const [iconColor, setIconColor] = useState(""); 47 | const [summaryMessageKey, setSummaryMessageKey] = useState("placeholder"); 48 | const [buttonMessageKey, setButtonMessageKey] = useState("placeholder"); 49 | 50 | useLayoutEffect(() => { 51 | if (loggedIn) { 52 | setIcon(faLockOpen); 53 | setIconColor(selectedTheme.success); 54 | setSummaryMessageKey("drawer.wallet.connect.logout"); 55 | setButtonMessageKey("drawer.wallet.connect.action.logout"); 56 | } else if (selectedWeb3Account) { 57 | setIcon(faLock); 58 | setIconColor(selectedTheme.error); 59 | setSummaryMessageKey("drawer.wallet.connect.login"); 60 | setButtonMessageKey("drawer.wallet.connect.action.login"); 61 | } else { 62 | setIcon(faPlug); 63 | setIconColor(selectedTheme.primary); 64 | setSummaryMessageKey("drawer.wallet.connect.summary"); 65 | setButtonMessageKey("drawer.wallet.connect.action.connect"); 66 | } 67 | }, [loggedIn, selectedWeb3Account]); 68 | 69 | const handleButtonClick = useCallback(() => { 70 | if (loggedIn) { 71 | onLogout(); 72 | } else if (selectedWeb3Account) { 73 | onLogin(); 74 | } else { 75 | onConnectWallet(); 76 | } 77 | }, [ 78 | loggedIn, 79 | onConnectWallet, 80 | onLogin, 81 | onLogout, 82 | selectedWeb3Account, 83 | ]); 84 | 85 | return ( 86 | 92 | 93 | 94 | {selectedWeb3Account ? ( 95 | getShortenedEthereumAddress(selectedWeb3Account) 96 | ) : ( 97 | 98 | )} 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | ); 139 | }; 140 | 141 | Drawer.propTypes = { 142 | open: PropTypes.bool.isRequired, 143 | onClose: PropTypes.func.isRequired, 144 | onConnectWallet: PropTypes.func.isRequired, 145 | selectedWeb3Account: PropTypes.string, 146 | onLogin: PropTypes.func.isRequired, 147 | onLogout: PropTypes.func.isRequired, 148 | loggedIn: PropTypes.bool.isRequired, 149 | darkTheme: PropTypes.bool.isRequired, 150 | onDarkThemeChange: PropTypes.func.isRequired, 151 | selectedLanguage: PropTypes.string.isRequired, 152 | onSelectedLanguageChange: PropTypes.func.isRequired, 153 | }; 154 | -------------------------------------------------------------------------------- /src/components/drawer/sections/exchange-info/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ListItemHeader, ListItemBox, ListItemIcon } from "../../styled"; 3 | import { FormattedMessage } from "react-intl"; 4 | import { 5 | faDollarSign, 6 | faClipboardCheck, 7 | faFileSignature, 8 | } from "@fortawesome/free-solid-svg-icons"; 9 | import { UndecoratedLink } from "../../../undecorated-link"; 10 | 11 | export const ExchangeInfo = () => { 12 | return ( 13 | <> 14 | 15 | 16 | 17 | 22 | 23 | {" "} 24 | 25 | 26 | 27 | 32 | 33 | {" "} 34 | 35 | 36 | 37 | 42 | 43 | {" "} 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/drawer/sections/lrc/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ListItemHeader, ListItemBox, ListItemIcon } from "../../styled"; 3 | import { FormattedMessage } from "react-intl"; 4 | import { faMagnet } from "@fortawesome/free-solid-svg-icons"; 5 | import { UndecoratedLink } from "../../../undecorated-link"; 6 | 7 | export const Lrc = () => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 18 | 19 | {" "} 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/drawer/sections/settings/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { ListItemHeader } from "../../styled"; 4 | import { FormattedMessage } from "react-intl"; 5 | import { Flex, Box } from "reflexbox"; 6 | import { Switch } from "../../../switch"; 7 | import { TextBox } from "./styled"; 8 | import { Chip } from "../../../chip"; 9 | 10 | export const Settings = ({ 11 | darkTheme, 12 | onDarkThemeChange, 13 | selectedLanguage, 14 | onSelectedLanguageChange, 15 | }) => { 16 | const getLanguageChipClickHandler = (language) => () => { 17 | onSelectedLanguageChange(language); 18 | }; 19 | 20 | return ( 21 | <> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | IT 44 | 45 | 46 | 47 | 51 | EN 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | }; 59 | 60 | Settings.propTypes = { 61 | darkTheme: PropTypes.bool.isRequired, 62 | onDarkThemeChange: PropTypes.func.isRequired, 63 | selectedLanguage: PropTypes.string.isRequired, 64 | onSelectedLanguageChange: PropTypes.func.isRequired, 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/drawer/sections/settings/styled.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Box } from "reflexbox"; 3 | 4 | export const TextBox = styled(Box)` 5 | color: ${(props) => props.theme.text}; 6 | transition: color 0.3s ease; 7 | display: flex; 8 | align-items: center; 9 | font-size: 12px; 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/drawer/sections/support/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ListItemHeader, ListItemBox, ListItemIcon } from "../../styled"; 3 | import { FormattedMessage } from "react-intl"; 4 | import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; 5 | import { faTelegram, faDiscord } from "@fortawesome/free-brands-svg-icons"; 6 | import { UndecoratedLink } from "../../../undecorated-link"; 7 | 8 | export const Support = () => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 19 | 20 | {" "} 21 | 22 | 23 | 24 | 29 | 30 | {" "} 31 | 32 | 33 | 34 | 39 | 40 | {" "} 41 | 42 | 43 | 44 | 49 | 50 | {" "} 51 | 52 | 53 | 54 | 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/drawer/sections/technical-resources/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ListItemHeader, ListItemBox, ListItemIcon } from "../../styled"; 3 | import { FormattedMessage } from "react-intl"; 4 | import { 5 | faFile, 6 | faPencilRuler, 7 | faRulerCombined, 8 | faCode, 9 | } from "@fortawesome/free-solid-svg-icons"; 10 | import { UndecoratedLink } from "../../../undecorated-link"; 11 | import { getEtherscanLink } from "../../../../lightcone/api/localStorgeAPI"; 12 | import { CHAIN_ID, DEX_SMART_CONTRACT_ADDRESS } from "../../../../env"; 13 | 14 | export const TechnicalResources = () => { 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 25 | 26 | {" "} 27 | 28 | 29 | 30 | 37 | 38 | {" "} 39 | 40 | 41 | 42 | 47 | 48 | {" "} 49 | 50 | 51 | 52 | 57 | 58 | {" "} 59 | 60 | 61 | 62 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/drawer/styled.js: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 2 | import { Flex, Box } from "reflexbox"; 3 | import styled from "styled-components"; 4 | 5 | export const RootFlex = styled(Flex)` 6 | width: 272px; 7 | position: fixed; 8 | top: 0; 9 | bottom: 0; 10 | right: 0; 11 | transition: transform 0.3s ease, background 0.3s ease, box-shadow 0.3s ease; 12 | transform: translateX(${(props) => (props.open ? "0" : "150%")}); 13 | background: ${(props) => props.theme.background}; 14 | box-shadow: 0px 30px 62px 0px ${(props) => props.theme.shadow}; 15 | overflow-y: auto; 16 | `; 17 | 18 | export const HeaderFlex = styled(Flex)` 19 | width: 100%; 20 | height: 60px; 21 | min-height: 60px; 22 | align-items: center; 23 | justify-content: space-between; 24 | padding: 8px 16px; 25 | font-size: 20px; 26 | font-weight: 700; 27 | color: ${(props) => props.theme.text}; 28 | transition: color 0.3s ease; 29 | `; 30 | 31 | export const SummaryMessage = styled.span` 32 | font-size: 12px; 33 | color: ${(props) => props.theme.text}; 34 | transition: color 0.3s ease; 35 | `; 36 | 37 | export const EllipsizedBox = styled(Box)` 38 | overflow: hidden; 39 | text-overflow: ellipsis; 40 | white-space: nowrap; 41 | `; 42 | 43 | export const Icon = styled(FontAwesomeIcon)` 44 | font-size: 40px; 45 | `; 46 | 47 | export const CloseIcon = styled(FontAwesomeIcon)` 48 | cursor: pointer; 49 | `; 50 | 51 | export const ListItemHeader = styled(Box)` 52 | display: flex; 53 | align-items: center; 54 | width: 100%; 55 | padding: 0 20px; 56 | font-size: 12px; 57 | font-weight: 600; 58 | color: ${(props) => props.theme.textLight}; 59 | margin-bottom: 16px; 60 | transition: color 0.3s ease; 61 | `; 62 | 63 | export const ListItemBox = styled(Box)` 64 | cursor: pointer; 65 | display: flex; 66 | align-items: center; 67 | width: 100%; 68 | padding: 0 20px; 69 | min-height: 44px; 70 | height: 44px; 71 | font-size: 12px; 72 | color: ${(props) => props.theme.text}; 73 | background: ${(props) => props.theme.background}; 74 | :hover { 75 | color: ${(props) => props.theme.textInverted}; 76 | background: ${(props) => props.theme.primary}; 77 | } 78 | transition: color 0.3s ease, background 0.3s ease; 79 | `; 80 | 81 | export const ListItemIcon = styled(FontAwesomeIcon)` 82 | margin-right: 8px; 83 | `; 84 | -------------------------------------------------------------------------------- /src/components/full-screen-overlay/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const FullScreenOverlay = styled.div` 4 | position: fixed; 5 | top: 0; 6 | bottom: 0; 7 | left: 0; 8 | right: 0; 9 | background: ${(props) => props.theme.shadow}; 10 | opacity: ${(props) => (props.open ? "1" : "0")}; 11 | transform: translateY(${(props) => (props.open ? "0" : "150%")}); 12 | transition: ${(props) => 13 | props.open 14 | ? "opacity 0.3s ease" 15 | : "transform 0.3s ease 0.3s, opacity 0.3s ease"}, 16 | background 0.3s ease; 17 | `; 18 | -------------------------------------------------------------------------------- /src/components/invalid-chain-id/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Flex, Box } from "reflexbox"; 3 | import { StyledFontAwesomeIcon, Text } from "./styled"; 4 | import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; 5 | import { FormattedMessage } from "react-intl"; 6 | import { useState } from "react"; 7 | import { useEffect } from "react"; 8 | import { CHAIN_ID } from "../../env"; 9 | 10 | export const InvalidChainId = () => { 11 | const [chainName, setChainName] = useState(""); 12 | 13 | useEffect(() => { 14 | let chainName; 15 | switch (CHAIN_ID) { 16 | case 1: { 17 | chainName = "mainnet"; 18 | break; 19 | } 20 | case 5: { 21 | chainName = "Goerli"; 22 | break; 23 | } 24 | default: { 25 | chainName = "unknown"; 26 | } 27 | } 28 | setChainName(chainName); 29 | }, []); 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/invalid-chain-id/styled.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | 4 | export const StyledFontAwesomeIcon = styled(FontAwesomeIcon)` 5 | font-size: 60px; 6 | color: ${(props) => props.theme.error}; 7 | `; 8 | 9 | export const Text = styled.span` 10 | color: ${(props) => props.theme.error}; 11 | `; 12 | -------------------------------------------------------------------------------- /src/components/layout/footer/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FlexContainer, WarningText } from "./styled"; 3 | import { version } from "../../../../package.json"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { faGithub } from "@fortawesome/free-brands-svg-icons"; 6 | import { UndecoratedLink } from "../../undecorated-link"; 7 | import { FormattedMessage } from "react-intl"; 8 | import { Box } from "reflexbox"; 9 | 10 | export const Footer = () => ( 11 | 12 | 13 | Powered by Ethereum & Loopring · v{version} ·{" "} 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/components/layout/footer/styled.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Flex } from "reflexbox"; 3 | 4 | export const FlexContainer = styled(Flex)` 5 | padding-left: 20px; 6 | padding-right: 20px; 7 | width: 100%; 8 | align-items: center; 9 | justify-content: center; 10 | font-size: 12px; 11 | `; 12 | 13 | export const WarningText = styled.span` 14 | color: ${(props) => props.theme.error}; 15 | `; 16 | -------------------------------------------------------------------------------- /src/components/layout/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Toolbar } from "./toolbar"; 3 | import PropTypes from "prop-types"; 4 | import { Root, Content } from "./styled"; 5 | import { Footer } from "./footer"; 6 | 7 | export const Layout = ({ children, onDrawerOpenClick }) => ( 8 | 9 | 10 | {children} 11 |