├── CNAME ├── src ├── utils │ ├── Common.scss.d.ts │ ├── forms │ │ ├── Checkbox.scss.d.ts │ │ ├── ErrorMessage.scss.d.ts │ │ ├── Radio.scss.d.ts │ │ ├── icon_arrow_down.svg │ │ ├── icon_arrow_down_grey.svg │ │ ├── Select.scss.d.ts │ │ ├── ErrorMessage.scss │ │ ├── Slider.scss.d.ts │ │ ├── InputGroup.scss.d.ts │ │ ├── switch-active.svg │ │ ├── Buttons.scss.d.ts │ │ ├── Buttons.mixins.scss │ │ ├── Checkbox.tsx │ │ ├── Slider.stories.tsx │ │ ├── Radio.stories.tsx │ │ ├── Radio.tsx │ │ ├── Select.tsx │ │ ├── InputGroup.scss │ │ ├── Select.scss │ │ ├── Slider.scss │ │ ├── Slider.tsx │ │ ├── InputGroup.tsx │ │ ├── switch-disabled.svg │ │ ├── Radio.scss │ │ └── ErrorMessage.tsx │ ├── gasCost │ │ ├── GasCost.scss.d.ts │ │ ├── GasCost.scss │ │ └── GasCost.tsx │ ├── layout │ │ ├── FlexLayoutRow.scss.d.ts │ │ ├── FlexLayoutRow.scss │ │ ├── LayoutHelpers.scss.d.ts │ │ ├── FlexLayoutRow.tsx │ │ ├── LayoutHelpers.scss │ │ └── LayoutHelpers.tsx │ ├── Scrollbar │ │ ├── Scrollbar.scss.d.ts │ │ ├── Scrollbar.scss │ │ └── Scrollbar.tsx │ ├── bigNumberInput │ │ ├── BigNumberInput.scss.d.ts │ │ ├── BigNumberInput.scss │ │ └── BigNumberInput.tsx │ ├── omit.ts │ ├── types.ts │ ├── tabs │ │ ├── Tabs.scss.d.ts │ │ ├── Tabs.scss │ │ └── StaticTabs.tsx │ ├── loadingIndicator │ │ ├── LoadingIndicator.scss.d.ts │ │ ├── LoadingIndicatorDark.gif │ │ ├── LoadingIndicatorLight.gif │ │ ├── LoggedOut.scss.d.ts │ │ ├── ServerUnreachable.scss.d.ts │ │ ├── Gate.tsx │ │ ├── LoadingIndicator.scss │ │ ├── LoggedOut.scss │ │ ├── ServerUnreachable.scss │ │ ├── Authorization.tsx │ │ ├── LoggedOut.tsx │ │ └── ServerUnreachable.tsx │ ├── observableItem.ts │ ├── tooltip │ │ ├── Tooltip.scss.d.ts │ │ └── Tooltip.tsx │ ├── UnreachableCaseError.ts │ ├── Approximate.tsx │ ├── zero.ts │ ├── text │ │ ├── Text.scss.d.ts │ │ ├── Text.scss │ │ └── TransactionStateDescription.tsx │ ├── guid.ts │ ├── table │ │ ├── Table.scss.d.ts │ │ └── Table.tsx │ ├── icons │ │ ├── Icons.scss.d.ts │ │ ├── Icons.stories.tsx │ │ ├── utils.tsx │ │ ├── SocialIcons.tsx │ │ ├── Icons.tsx │ │ └── Icons.scss │ ├── slippage.ts │ ├── inject.tsx │ ├── testHelpers.ts │ ├── impossible.ts │ ├── collections.ts │ ├── panel │ │ ├── Panel.scss.d.ts │ │ └── TopRightCorner.tsx │ ├── markets.ts │ ├── authorizable.ts │ ├── Common.scss │ ├── connect.tsx │ ├── Timer.tsx │ ├── loadable.ts │ ├── price.ts │ ├── operators.ts │ ├── markets.test.ts │ ├── combineAndMerge.ts │ ├── modal.tsx │ ├── switchSpread.ts │ └── price.test.ts ├── index.scss.d.ts ├── landingPage │ ├── client │ │ ├── Client.scss.d.ts │ │ ├── Client.scss │ │ └── Client.tsx │ ├── LandingPage.scss.d.ts │ ├── Banner.scss.d.ts │ └── Banner.tsx ├── exchange │ ├── priceChart │ │ ├── PriceChartWithLoading.scss.d.ts │ │ ├── PriceChartWithLoading.scss │ │ ├── pricechart.test.ts │ │ └── PriceChartView.scss.d.ts │ ├── allTrades │ │ ├── AllTradesView.scss.d.ts │ │ └── AllTradesView.scss │ ├── orderbook │ │ ├── OrderbookView.scss.d.ts │ │ ├── depth-chart.svg │ │ └── OrderbookView.scss │ ├── offerMake │ │ ├── OfferMakePanel.scss │ │ ├── OfferMakePanel.scss.d.ts │ │ ├── OfferMakeForm.scss.d.ts │ │ └── OfferMakePanel.tsx │ ├── depthChart │ │ ├── minus.svg │ │ ├── plus.svg │ │ ├── orderbook.svg │ │ └── DepthChartView.scss.d.ts │ ├── ExchangeView.scss.d.ts │ ├── myTrades │ │ ├── MyTradesView.scss.d.ts │ │ ├── closedTrades.ts │ │ └── MyTradesView.scss │ ├── tradingPair │ │ └── TradingPairView.scss.d.ts │ ├── OrderbookPanel.tsx │ └── ExchangeView.scss ├── instant │ ├── asset │ │ ├── Asset.scss.d.ts │ │ ├── Asset.scss │ │ └── Asset.tsx │ ├── details │ │ ├── TradeSummary.scss.d.ts │ │ ├── TxStatusRow.scss.d.ts │ │ ├── TradeData.scss.d.ts │ │ ├── TradeSummary.scss │ │ ├── TradeDetails.scss │ │ ├── TxStatusRow.tsx │ │ ├── TxStatusRow.scss │ │ ├── TradeDetails.scss.d.ts │ │ ├── TradeData.scss │ │ └── TradeData.tsx │ ├── views │ │ ├── AssetSelectorView.scss.d.ts │ │ ├── AllowancesView.scss.d.ts │ │ ├── TradeSettingsView.scss.d.ts │ │ ├── AccountView.scss.d.ts │ │ ├── PriceImpactWarningView.scss.d.ts │ │ ├── AssetSelectorView.scss │ │ ├── AllowancesView.scss │ │ └── TradeSettingsView.scss │ ├── progress │ │ ├── ProgressReport.scss.d.ts │ │ └── ProgressReport.scss │ ├── Instant.scss.d.ts │ ├── CurrentPrice.tsx │ ├── InstantFormWrapper.tsx │ └── instantDevModeHelpers.ts ├── footer │ ├── Footer.scss.d.ts │ └── Footer.scss ├── storybookUtils.ts ├── icons │ ├── arrow-down.svg │ ├── swap-arrows.svg │ ├── close.svg │ ├── checkbox.svg │ ├── coins │ │ ├── bat.svg │ │ ├── bat-circle.svg │ │ ├── dgd.svg │ │ ├── dai.svg │ │ ├── eth.svg │ │ ├── bat-color.svg │ │ ├── dgd-circle.svg │ │ ├── dai-circle.svg │ │ ├── eth-inverse.svg │ │ ├── eth-color-inverse.svg │ │ ├── dai-color.svg │ │ ├── dgd-color.svg │ │ ├── dai-inverse.svg │ │ ├── eth-color.svg │ │ ├── eth-circle.svg │ │ ├── mkr.svg │ │ └── mkr-inverse.svg │ ├── chevron-down.svg │ ├── chevron-up.svg │ ├── checkbox-active.svg │ ├── error.svg │ ├── warning.svg │ ├── info.svg │ ├── radio.svg │ ├── done.svg │ ├── account.svg │ ├── providers │ │ ├── ledger.svg │ │ ├── ethereum.svg │ │ ├── parity-white.svg │ │ ├── web-wallet.svg │ │ ├── status-white.svg │ │ ├── metamask-white.svg │ │ ├── coinbase.svg │ │ ├── status.svg │ │ └── parity.svg │ ├── back.svg │ ├── cross.svg │ └── cog-wheel.svg ├── balances │ ├── TaxExporter.scss.d.ts │ ├── AssetOverviewView.scss.d.ts │ ├── BalancesView.tsx │ └── TaxExporter.scss ├── transactionNotifier │ ├── TransactionNotifier.scss.d.ts │ └── transactionNotifier.ts ├── blockchain │ ├── user.ts │ ├── utils.ts │ ├── etherscan.ts │ ├── web3.ts │ ├── abi │ │ ├── proxy-registry.abi.json │ │ └── ds-proxy-factory.abi.json │ └── calls │ │ ├── txMeta.ts │ │ └── wrapUnwrapCalls.tsx ├── header │ ├── Network.tsx │ ├── WalletConnection.scss.d.ts │ └── Header.scss.d.ts ├── wrapUnwrap │ ├── WrapUnwrapFormView.scss.d.ts │ └── __snapshots__ │ │ └── wrapUnwrapForm.test.ts.snap └── index.scss ├── .storybook ├── addons.js ├── disable-animations.js ├── config.js ├── webpack.config.js └── preview-head.html ├── public ├── tos.pdf ├── favicon.ico └── manifest.json ├── images.d.ts ├── config.d.ts ├── cypress ├── utils │ └── untyped.d.ts ├── tsconfig.json ├── fixtures │ └── example.json ├── pages │ ├── Tab.js │ ├── TradeSettings.ts │ ├── Tab.ts │ ├── Balance.ts │ ├── TradeData.ts │ ├── Proxy.ts │ ├── WrapUnwrap.ts │ ├── Summary.ts │ ├── Allowance.ts │ ├── Orderbook.ts │ ├── Trades.ts │ ├── Order.ts │ └── TradingPairDropdown.ts ├── integration │ ├── OrderbookView.spec.ts │ ├── Balances.spec.ts │ ├── Allowances.spec.ts │ └── instant │ │ └── AccountSettings.spec.ts ├── support │ ├── index.js │ └── commands.js └── plugins │ └── index.js ├── .env ├── scripts ├── dev.sh ├── test.js └── docker-compose.yml ├── .env.dev ├── .env.production ├── deploy-prod.sh ├── tsconfig.test.json ├── config ├── jest │ ├── typescriptTransform.js │ ├── fileTransform.js │ └── cssTransform.js ├── buildInfo.js └── polyfills.js ├── .circleci ├── run-e2e.sh └── install-yarn.sh ├── tsconfig.prod.json ├── cypress.json ├── regconfig.json ├── tsconfig.compiler.json ├── codechecks.yml ├── .gitignore ├── tsconfig.json ├── tslint.json ├── codechecks-e2e-vis-reg.ts └── codechecks.ts /CNAME: -------------------------------------------------------------------------------- 1 | eth2dai.com -------------------------------------------------------------------------------- /src/utils/Common.scss.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /src/index.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const container: string; 2 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import 'storybook-chrome-screenshot/register'; -------------------------------------------------------------------------------- /src/landingPage/client/Client.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const client: string; 2 | -------------------------------------------------------------------------------- /src/utils/forms/Checkbox.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const checkbox: string; 2 | -------------------------------------------------------------------------------- /src/utils/forms/ErrorMessage.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const errors: string; 2 | -------------------------------------------------------------------------------- /src/utils/gasCost/GasCost.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const unknownIcon: string; 2 | -------------------------------------------------------------------------------- /src/utils/layout/FlexLayoutRow.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const flr: string; 2 | -------------------------------------------------------------------------------- /public/tos.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/eth2dai/HEAD/public/tos.pdf -------------------------------------------------------------------------------- /src/utils/Scrollbar/Scrollbar.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const scrollbarThumb: string; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/eth2dai/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/exchange/priceChart/PriceChartWithLoading.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const btn: string; 2 | -------------------------------------------------------------------------------- /src/utils/bigNumberInput/BigNumberInput.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const inputBase: string; 2 | -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | -------------------------------------------------------------------------------- /src/utils/omit.ts: -------------------------------------------------------------------------------- 1 | export type Omit = Pick>; 2 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type UnionDictionary = { [k in K]: V }; 2 | -------------------------------------------------------------------------------- /config.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.json" { 2 | const value: any; 3 | export default value; 4 | } -------------------------------------------------------------------------------- /cypress/utils/untyped.d.ts: -------------------------------------------------------------------------------- 1 | declare module "web3"; 2 | declare module "truffle-privatekey-provider"; -------------------------------------------------------------------------------- /src/utils/forms/Radio.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const radio: string; 2 | export const hasError: string; 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_INSTANT_ENABLED=1 2 | REACT_APP_TAX_EXPORTER_ENABLED=1 3 | REACT_APP_OASIS_DEX_ENABLED=1 4 | -------------------------------------------------------------------------------- /scripts/dev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | 5 | docker-compose up -d 6 | -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | REACT_APP_INSTANT_ENABLED=1 2 | REACT_APP_TAX_EXPORTER_ENABLED=1 3 | REACT_APP_OASIS_DEX_ENABLED=1 4 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_INSTANT_ENABLED=1 2 | REACT_APP_TAX_EXPORTER_ENABLED=1 3 | REACT_APP_OASIS_DEX_ENABLED=0 4 | -------------------------------------------------------------------------------- /src/utils/Scrollbar/Scrollbar.scss: -------------------------------------------------------------------------------- 1 | .scrollbarThumb { 2 | background-color: #eee; 3 | border-radius: 5.5px; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/utils/tabs/Tabs.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const Tabs: string; 2 | export const tabs: string; 3 | export const button: string; 4 | -------------------------------------------------------------------------------- /src/instant/asset/Asset.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const asset: string; 2 | export const locked: string; 3 | export const icon: string; 4 | -------------------------------------------------------------------------------- /src/utils/gasCost/GasCost.scss: -------------------------------------------------------------------------------- 1 | .unknownIcon { 2 | display: inline-block; 3 | vertical-align: middle; 4 | margin-left: 0.5em; 5 | } 6 | -------------------------------------------------------------------------------- /src/instant/details/TradeSummary.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const details: string; 2 | export const summary: string; 3 | export const highlight: string; 4 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoadingIndicator.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const loading: string; 2 | export const inline: string; 3 | export const light: string; 4 | -------------------------------------------------------------------------------- /src/exchange/allTrades/AllTradesView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const allTradesTable: string; 2 | export const loadMore: string; 3 | export const loader: string; 4 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoadingIndicatorDark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/eth2dai/HEAD/src/utils/loadingIndicator/LoadingIndicatorDark.gif -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoadingIndicatorLight.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/eth2dai/HEAD/src/utils/loadingIndicator/LoadingIndicatorLight.gif -------------------------------------------------------------------------------- /src/utils/observableItem.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/index'; 2 | 3 | export type ObservableItem = T extends Observable ? U : never; 4 | -------------------------------------------------------------------------------- /deploy-prod.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./build 2 | 3 | yarn build 4 | 5 | echo "eth2dai.com" > ./build/CNAME 6 | 7 | cp ./build/200.html ./build/404.html 8 | 9 | gh-pages -d ./build -------------------------------------------------------------------------------- /src/footer/Footer.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const links: string; 2 | export const footerSeparator: string; 3 | export const footer: string; 4 | export const iconLink: string; 5 | -------------------------------------------------------------------------------- /src/utils/tooltip/Tooltip.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const warningIcon: string; 2 | export const grey: string; 3 | export const white: string; 4 | export const softCyan: string; 5 | -------------------------------------------------------------------------------- /src/instant/details/TxStatusRow.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const txStatusRow: string; 2 | export const icon: string; 3 | export const label: string; 4 | export const status: string; 5 | -------------------------------------------------------------------------------- /src/utils/UnreachableCaseError.ts: -------------------------------------------------------------------------------- 1 | export class UnreachableCaseError extends Error { 2 | constructor(val: never) { 3 | super(`Unreachable case: ${val}`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "types": ["cypress", "mocha"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoggedOut.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const block: string; 2 | export const icon: string; 3 | export const mainInfo: string; 4 | export const annotate: string; 5 | -------------------------------------------------------------------------------- /src/instant/views/AssetSelectorView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const assetsContainer: string; 2 | export const assets: string; 3 | export const list: string; 4 | export const listItem: string; 5 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.prod.json", 3 | "compilerOptions": { 4 | "types": ["jest"], 5 | "module": "commonjs", 6 | }, 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /src/storybookUtils.ts: -------------------------------------------------------------------------------- 1 | export function ignoreDuringVisualRegression(storyCreator: () => any) { 2 | if (!navigator.userAgent.match(/HeadlessChrome/)) { 3 | storyCreator(); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/Approximate.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const Approximate = (props: any) => ( 4 | 5 | ~ {props.children} 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/exchange/orderbook/OrderbookView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const orderbook: string; 2 | export const switchBtn: string; 3 | export const spreadRow: string; 4 | export const orderbookTable: string; 5 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /src/exchange/offerMake/OfferMakePanel.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/panel/Panel"; 2 | 3 | .loaderWithFooterBordered { 4 | @extend .panelWithFooterBordered; 5 | display: flex; 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/instant/details/TradeData.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const entry: string; 2 | export const reversed: string; 3 | export const label: string; 4 | export const value: string; 5 | export const infoIcon: string; 6 | -------------------------------------------------------------------------------- /src/utils/zero.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | 3 | export const zero = new BigNumber('0'); 4 | export const one = new BigNumber('1'); 5 | export const minusOne = new BigNumber('-1'); 6 | -------------------------------------------------------------------------------- /src/balances/TaxExporter.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const taxExporterDescription: string; 2 | export const taxExporterPanelBody: string; 3 | export const taxExporterButton: string; 4 | export const progressIcon: string; 5 | -------------------------------------------------------------------------------- /config/jest/typescriptTransform.js: -------------------------------------------------------------------------------- 1 | // Copyright 2004-present Facebook. All Rights Reserved. 2 | 3 | 'use strict'; 4 | 5 | const tsJestPreprocessor = require('ts-jest/preprocessor'); 6 | 7 | module.exports = tsJestPreprocessor; 8 | -------------------------------------------------------------------------------- /src/utils/layout/FlexLayoutRow.scss: -------------------------------------------------------------------------------- 1 | @import "../Mixins"; 2 | 3 | .flr { 4 | display: flex; 5 | justify-content: space-between; 6 | 7 | @include media-breakpoint-down(lg) { 8 | flex-wrap: wrap; 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/exchange/priceChart/PriceChartWithLoading.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Mixins"; 2 | 3 | .btn { 4 | width: 42px; 5 | 6 | @include media-breakpoint-down(sm){ 7 | width: 34px; 8 | font-size: 11px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/icons/swap-arrows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/instant/views/AllowancesView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const assets: string; 2 | export const asset: string; 3 | export const tokenIcon: string; 4 | export const indicator: string; 5 | export const isAllowed: string; 6 | export const disabled: string; 7 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/ServerUnreachable.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const block: string; 2 | export const inline: string; 3 | export const icon: string; 4 | export const mainInfo: string; 5 | export const annotate: string; 6 | export const link: string; 7 | -------------------------------------------------------------------------------- /src/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/instant/progress/ProgressReport.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const progressReport: string; 2 | export const progressIcon: string; 3 | export const success: string; 4 | export const link: string; 5 | export const description: string; 6 | export const failure: string; 7 | -------------------------------------------------------------------------------- /src/utils/forms/icon_arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/forms/icon_arrow_down_grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/exchange/depthChart/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/utils/text/Text.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const muted: string; 2 | export const infoLabel: string; 3 | export const buySellSpan: string; 4 | export const redSpan: string; 5 | export const greenSpan: string; 6 | export const currency: string; 7 | export const highlight: string; 8 | -------------------------------------------------------------------------------- /.circleci/run-e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | cd ../ 5 | 6 | ./node_modules/.bin/http-server -p 3000 ./build & 7 | server_pid=$! 8 | 9 | mkdir -p ./cypress/screenshots 10 | 11 | yarn cypress:run:ci 12 | 13 | kill -9 $server_pid 14 | -------------------------------------------------------------------------------- /src/utils/layout/LayoutHelpers.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const borderBox: string; 2 | export const smPadding: string; 3 | export const mdPadding: string; 4 | export const nonePadding: string; 5 | export const hr: string; 6 | export const lightHr: string; 7 | export const darkHr: string; 8 | -------------------------------------------------------------------------------- /src/utils/layout/FlexLayoutRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import * as styles from './FlexLayoutRow.scss'; 4 | 5 | export const FlexLayoutRow = ({ children }: { children: any }) => ( 6 |
7 | {children} 8 |
9 | ); 10 | -------------------------------------------------------------------------------- /src/exchange/depthChart/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/coins/bat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/guid.ts: -------------------------------------------------------------------------------- 1 | export const guid = () => { 2 | const s4 = () => { 3 | return Math.floor((1 + Math.random()) * 0x10000) 4 | .toString(16) 5 | .substring(1); 6 | }; 7 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 8 | s4() + '-' + s4() + s4() + s4(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/instant/views/TradeSettingsView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const settings: string; 2 | export const parameter: string; 3 | export const name: string; 4 | export const value: string; 5 | export const warning: string; 6 | export const icon: string; 7 | export const text: string; 8 | export const highlight: string; 9 | -------------------------------------------------------------------------------- /src/utils/table/Table.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const table: string; 2 | export const numerical: string; 3 | export const extendOnBorders: string; 4 | export const right: string; 5 | export const center: string; 6 | export const left: string; 7 | export const trHighlighted: string; 8 | export const clickable: string; 9 | -------------------------------------------------------------------------------- /cypress/pages/Tab.js: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | export class Tab { 3 | } 4 | Tab.exchange = () => { 5 | cy.get(tid('Exchange')).click(); 6 | }; 7 | Tab.margin = () => { 8 | cy.get(tid('Margin')).click(); 9 | }; 10 | Tab.balances = () => { 11 | cy.get(tid('Balances')).click(); 12 | }; 13 | -------------------------------------------------------------------------------- /src/exchange/ExchangeView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const tradingPairPanel: string; 2 | export const pairPickerOpen: string; 3 | export const priceChartPanel: string; 4 | export const allTradesPanel: string; 5 | export const offerMakePanel: string; 6 | export const orderbookPanel: string; 7 | export const myOrdersPanel: string; 8 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.compiler.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "types": [] 6 | }, 7 | "include": ["src/**/*.ts", "src/**/*.tsx", "*.d.ts", "./node_modules/web3-typescript-typings/index.d.ts"], 8 | "exclude": ["src/**/*.test.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/balances/AssetOverviewView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const table: string; 2 | export const center: string; 3 | export const amount: string; 4 | export const centeredAsset: string; 5 | export const transferBtn: string; 6 | export const disabled: string; 7 | export const transferBtnLeft: string; 8 | export const wrapUnwrapBtn: string; 9 | -------------------------------------------------------------------------------- /src/icons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/utils/icons/Icons.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const infoIcon: string; 2 | export const progressIcon: string; 3 | export const spin: string; 4 | export const progressIconLight: string; 5 | export const progressIconSm: string; 6 | export const progressIconLg: string; 7 | export const socialIcon: string; 8 | export const btnIcon: string; 9 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "projectId": "noiqfs", 4 | "videoUploadOnPasses": false, 5 | "defaultCommandTimeout": 40000, 6 | "requestTimeout": 10000, 7 | "viewportWidth": 1024, 8 | "viewportHeight": 960, 9 | "env": { 10 | "ETH_PROVIDER": "http://localhost:8545" 11 | } 12 | } -------------------------------------------------------------------------------- /src/utils/forms/Select.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const select: string; 2 | export const wrapperBordered: string; 3 | export const wrapper: string; 4 | export const disabled: string; 5 | export const xs: string; 6 | export const sm: string; 7 | export const md: string; 8 | export const lg: string; 9 | export const unsized: string; 10 | -------------------------------------------------------------------------------- /src/icons/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/exchange/depthChart/orderbook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/checkbox-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /src/transactionNotifier/TransactionNotifier.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const main: string; 2 | export const block: string; 3 | export const title: string; 4 | export const close: string; 5 | export const icon: string; 6 | export const iconInner: string; 7 | export const description: string; 8 | export const link: string; 9 | export const cross: string; 10 | -------------------------------------------------------------------------------- /src/utils/slippage.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { NetworkConfig } from '../blockchain/config'; 3 | 4 | export const getSlippageLimit = (context: NetworkConfig, quotation: string): BigNumber => 5 | // @ts-ignore 6 | new BigNumber(context.thresholds[quotation 7 | .split('/') 8 | .join('').toLowerCase()] || 0.02); 9 | -------------------------------------------------------------------------------- /src/icons/coins/bat-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/coins/dgd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/forms/ErrorMessage.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | 3 | // errors 4 | .errors { 5 | display: flex; 6 | align-items: center; 7 | justify-content: flex-end; 8 | 9 | color: $red; 10 | font-size: 11px; 11 | height: 24px; 12 | text-align: right; 13 | 14 | a { 15 | color: $red; 16 | font-weight: bolder; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/icons/coins/dai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/landingPage/client/Client.scss: -------------------------------------------------------------------------------- 1 | .client { 2 | background: rgba(255,255,255,0.98); 3 | border-radius: 4px; 4 | padding: 1em; 5 | max-width: 208px; 6 | text-align: center; 7 | } 8 | 9 | a.client { 10 | text-decoration: none; 11 | cursor: pointer; 12 | display: inline-block; 13 | border: 1px solid black; 14 | margin: 0em 0.7em 0.7em 0.7em; 15 | } 16 | -------------------------------------------------------------------------------- /regconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "workingDir": ".reg", 4 | "actualDir": "__screenshots__", 5 | "thresholdRate": 0, 6 | "ximgdiff": { 7 | "invocationType": "client" 8 | } 9 | }, 10 | "plugins": { 11 | "reg-simple-keygen-plugin": { 12 | "expectedKey": "${CURRENT_KEY}", 13 | "actualKey": "${BASE_KEY}" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/utils/inject.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export function inject( 4 | Wrapped: React.ComponentType, 5 | props: B 6 | ): React.ComponentType { 7 | return class extends React.Component { 8 | public render() { 9 | return ; 10 | } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/testHelpers.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/index'; 2 | 3 | export function unpack(o: Observable): any { 4 | 5 | let r; 6 | 7 | o.subscribe( 8 | v => { r = v; }, 9 | e => { 10 | console.log('error', e, typeof(e)); 11 | r = e; 12 | } 13 | ); 14 | 15 | console.assert(r !== undefined); 16 | 17 | return r; 18 | } 19 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/exchange/myTrades/MyTradesView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const container: string; 2 | export const orderTypeBtn: string; 3 | export const right: string; 4 | export const myTradesTable: string; 5 | export const myOpenTradesTable: string; 6 | export const myCloseTradesTable: string; 7 | export const status: string; 8 | export const statusText: string; 9 | export const statusProgress: string; 10 | -------------------------------------------------------------------------------- /src/utils/impossible.ts: -------------------------------------------------------------------------------- 1 | export type Impossible = symbol; 2 | 3 | export function impossible(message: string): Impossible { 4 | return Symbol.for(message); 5 | } 6 | 7 | export function description(x: Impossible): string { 8 | return Symbol.keyFor(x)!; 9 | } 10 | 11 | export function isImpossible(x: T | Impossible): x is Impossible { 12 | return typeof x === 'symbol'; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/Gate.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface GateProps { 4 | children: React.ReactElement; 5 | isOpen: boolean; 6 | closed?: React.ReactElement; 7 | } 8 | 9 | export function Gate({ isOpen, closed, children }: GateProps) { 10 | if (!isOpen) { 11 | return closed ||

ooops!

; 12 | } 13 | return children; 14 | } 15 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/icons/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/instant/views/AccountView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const row: string; 2 | export const proxyMissing: string; 3 | export const text: string; 4 | export const accountIcon: string; 5 | export const warningIcon: string; 6 | export const proxyAvailable: string; 7 | export const warning: string; 8 | export const allowances: string; 9 | export const button: string; 10 | export const placeholder: string; 11 | -------------------------------------------------------------------------------- /src/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/utils/forms/Slider.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const wrapper: string; 2 | export const pointer: string; 3 | export const disabled: string; 4 | export const pointerBlocked: string; 5 | export const pointerUnblocked: string; 6 | export const moveOnHover: string; 7 | export const inProgress: string; 8 | export const progressIcon: string; 9 | export const progressBlocked: string; 10 | export const progressUnblocked: string; 11 | -------------------------------------------------------------------------------- /cypress/pages/TradeSettings.ts: -------------------------------------------------------------------------------- 1 | import { tid, timeout } from '../utils'; 2 | 3 | export class TradeSettings { 4 | public static button = () => cy.get(tid('trade-settings'), timeout(3000)); 5 | 6 | public static back = () => cy.get(tid('back')).click(); 7 | 8 | public static slippageLimit = (value: string) => 9 | cy.get(tid('slippage-limit'), timeout(2000)) 10 | .type(`{selectall}{backspace}${value}`) 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.compiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es2017", 7 | "lib": ["dom", "es2018"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "noImplicitReturns": true, 15 | } 16 | } -------------------------------------------------------------------------------- /src/instant/progress/ProgressReport.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | 3 | .progressReport { 4 | display: flex; 5 | align-items: center; 6 | } 7 | 8 | .progressIcon { 9 | margin-left: .5rem; 10 | } 11 | 12 | .success, .link { 13 | color: $very-light-blue; 14 | } 15 | 16 | .link { 17 | text-decoration: none; 18 | } 19 | 20 | .description { 21 | color: $grey-light 22 | } 23 | 24 | .failure { 25 | color: $errors; 26 | } 27 | -------------------------------------------------------------------------------- /codechecks.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | - name: build-size-watcher 3 | options: 4 | gzip: false 5 | files: 6 | - path: "./build/static/js/*.js" 7 | - path: "./build/static/css/*.css" 8 | 9 | - name: typecov 10 | options: 11 | tsconfigPath: "tsconfig.prod.json" 12 | 13 | - name: commit-deployment 14 | options: 15 | buildPath: "./build" 16 | 17 | settings: 18 | branches: 19 | - dev 20 | - master 21 | -------------------------------------------------------------------------------- /src/instant/views/PriceImpactWarningView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const container: string; 2 | export const impactText: string; 3 | export const continueText: string; 4 | export const graph: string; 5 | export const bar: string; 6 | export const bar1: string; 7 | export const bar2: string; 8 | export const bar3: string; 9 | export const bar4: string; 10 | export const danger: string; 11 | export const arrowPlaceholder: string; 12 | export const arrow: string; 13 | -------------------------------------------------------------------------------- /src/utils/forms/InputGroup.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const inputGroup: string; 2 | export const addon: string; 3 | export const xs: string; 4 | export const sm: string; 5 | export const md: string; 6 | export const lg: string; 7 | export const unsized: string; 8 | export const inputGroupAddonBorderRight: string; 9 | export const inputGroupAddonBorderLeft: string; 10 | export const grey: string; 11 | export const red: string; 12 | export const disabled: string; 13 | -------------------------------------------------------------------------------- /src/icons/coins/eth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/landingPage/LandingPage.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const section: string; 2 | export const logo: string; 3 | export const container: string; 4 | export const containerTopHalf: string; 5 | export const placeholder: string; 6 | export const column: string; 7 | export const containerBottomHalf: string; 8 | export const label: string; 9 | export const client: string; 10 | export const status: string; 11 | export const availableClients: string; 12 | export const unsupported: string; 13 | -------------------------------------------------------------------------------- /src/icons/coins/bat-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .prettierrc.yml 23 | .idea 24 | cypress/videos 25 | cypress/screenshots 26 | __screenshots__ 27 | -------------------------------------------------------------------------------- /cypress/pages/Tab.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | const defaultTimeout = { timeout: 10000 }; 4 | 5 | export class Tab { 6 | public static market = () => { 7 | cy.get(tid('Market'), { ...defaultTimeout }).click(); 8 | } 9 | 10 | public static balances = () => { 11 | cy.get(tid('Account'), { ...defaultTimeout }).click(); 12 | } 13 | 14 | public static instant = () => { 15 | cy.get(tid('Instant'), { ...defaultTimeout }).click(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/bigNumberInput/BigNumberInput.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | @import "../Mixins"; 3 | 4 | .inputBase { 5 | background: none; 6 | border: 0; 7 | color: $input-number-color; 8 | @include fontLatoBold; 9 | font-size: 15px; 10 | letter-spacing: 0.5px; 11 | padding-left: 1em; 12 | padding-right: .25em; 13 | text-align: right; 14 | width: 100%; 15 | min-width: 0; 16 | } 17 | 18 | .inputBase:disabled { 19 | color: $grey-light; // as in InputGroup.scss 20 | } -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoadingIndicator.scss: -------------------------------------------------------------------------------- 1 | .loading { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | flex-grow: 1; 6 | min-height: 50px; 7 | } 8 | 9 | .inline { 10 | display: inline-flex; 11 | width: 20px; 12 | flex-grow: 0; // reset loading's flex-grow: 1 13 | min-height: 20px; 14 | //margin-top: -4px; 15 | background-size: contain; 16 | } 17 | 18 | .light { 19 | background-image: url('./LoadingIndicatorLight.gif'); 20 | } 21 | -------------------------------------------------------------------------------- /src/icons/coins/dgd-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/coins/dai-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/collections.ts: -------------------------------------------------------------------------------- 1 | export function withPrevious(array: any[]): any[] { 2 | return array.reduce( 3 | ({ result, last }, item) => ({ result: result.concat([[item, last]]), last: item }), 4 | { result: [] } 5 | ).result; 6 | } 7 | 8 | export function withNext(array: T[]): Array<[T, T?]> { 9 | return array.reduceRight<{ result: Array<[T, T?]>, last?: T }>( 10 | ({ result, last }, item) => ({ result: [[item, last], ...result], last: item }), 11 | { result: [] } 12 | ).result; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/forms/switch-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/utils/panel/Panel.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const panel: string; 2 | export const panelWithFooterBordered: string; 3 | export const panelHeader: string; 4 | export const panelHeaderBordered: string; 5 | export const panelBodyHorizontal: string; 6 | export const panelBodyBottom: string; 7 | export const panelBodyTop: string; 8 | export const panelBodyScrollable: string; 9 | export const panelFooter: string; 10 | export const panelFooterBordered: string; 11 | export const topRightCorner: string; 12 | export const topLeftCorner: string; 13 | -------------------------------------------------------------------------------- /src/utils/icons/Icons.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { InfoIcon, ProgressIcon } from './Icons'; 4 | 5 | const stories = storiesOf('Icons', module); 6 | 7 | stories.add('Icons', () => ( 8 |
9 |

InfoIcon

10 | 11 |

ProgressIcon

12 | 13 |

ProgressIcon Light

14 | 15 |

ProgressIcon Small

16 | 17 |
18 | )); 19 | -------------------------------------------------------------------------------- /cypress/pages/Balance.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | export class Balance { 4 | public static of = (tokenSymbol: string) => { 5 | const symbol = tokenSymbol.toUpperCase(); 6 | cy.get(tid(`${symbol}-overview`), { timeout: 10000 }).as(`${symbol}`); 7 | 8 | return { 9 | shouldBe: (amount: string | number | RegExp) => { 10 | cy.get(`@${symbol}`).within(() => { 11 | (cy.get(tid(`${symbol}-balance`)) as any).contains(amount, { timeout: 10000 }); 12 | }); 13 | }, 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/radio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoggedOut.scss: -------------------------------------------------------------------------------- 1 | @import "../Mixins"; 2 | 3 | .block { 4 | @include fontMontserratMedium; 5 | 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: center; 10 | width: 100%; 11 | height: 100%; 12 | flex-grow: 1; 13 | } 14 | 15 | // block version elements 16 | .icon { 17 | margin-right: 0.5em; 18 | } 19 | 20 | .mainInfo { 21 | display: flex; 22 | align-items: center; 23 | } 24 | 25 | .annotate { 26 | margin-top: 0.8em; 27 | letter-spacing: .4px; 28 | font-size: 15px; 29 | } 30 | -------------------------------------------------------------------------------- /src/icons/coins/eth-inverse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/exchange/priceChart/pricechart.test.ts: -------------------------------------------------------------------------------- 1 | // import { Trade } from '../trades'; 2 | 3 | // import { priceChartDataPoints } from './pricechart'; 4 | 5 | // test('Empty trades equals empty priceChartData', () => { 6 | // expect(priceChartDataPoints([], (d: Date) => d)).toEqual([]); 7 | // }); 8 | 9 | test('hello', () => { 10 | expect(1 + 1).toEqual(2); 11 | }); 12 | 13 | // // const trades: Trade[] { 14 | // // { baseAmount: } 15 | // // } 16 | // // test('parse simple trades', () => { 17 | // // expect(priceChartDataPoints([], (d: Date) => d)).toEqual([]); 18 | // // }; 19 | -------------------------------------------------------------------------------- /src/utils/markets.ts: -------------------------------------------------------------------------------- 1 | import { eth2weth } from '../blockchain/calls/instant'; 2 | import { TradingPair } from '../exchange/tradingPair/tradingPair'; 3 | 4 | export const marketsOf = (token: string, allMarkets: TradingPair[]) => { 5 | return allMarkets.reduce((possibleMarkets, pair) => { 6 | if (token === pair.quote) { 7 | possibleMarkets.add(eth2weth(pair.base)); 8 | } 9 | 10 | if (token === pair.base) { 11 | possibleMarkets.add(eth2weth(pair.quote)); 12 | } 13 | return possibleMarkets; 14 | }, new Set()); 15 | }; 16 | -------------------------------------------------------------------------------- /src/blockchain/user.ts: -------------------------------------------------------------------------------- 1 | import { combineLatest, Observable } from 'rxjs'; 2 | import { map } from 'rxjs/operators'; 3 | 4 | import { account$ } from './network'; 5 | import { walletStatus$ } from './wallet'; 6 | 7 | export interface User { 8 | account?: string; 9 | authorized?: boolean; 10 | } 11 | 12 | export const user$: Observable = combineLatest(account$, walletStatus$).pipe( 13 | map(([account, walletStatus]) => { 14 | return { 15 | account: walletStatus === 'connected' ? account : undefined, 16 | authorized: undefined 17 | }; 18 | }), 19 | ); 20 | -------------------------------------------------------------------------------- /src/icons/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/exchange/offerMake/OfferMakePanel.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const panel: string; 2 | export const panelWithFooterBordered: string; 3 | export const loaderWithFooterBordered: string; 4 | export const panelHeader: string; 5 | export const panelHeaderBordered: string; 6 | export const panelBodyHorizontal: string; 7 | export const panelBodyBottom: string; 8 | export const panelBodyTop: string; 9 | export const panelBodyScrollable: string; 10 | export const panelFooter: string; 11 | export const panelFooterBordered: string; 12 | export const topRightCorner: string; 13 | export const topLeftCorner: string; 14 | -------------------------------------------------------------------------------- /.storybook/disable-animations.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const $iframe = document.getElementById("storybook-preview-iframe"); 3 | const $doc = $iframe.contentDocument; 4 | const $style = $doc.createElement("style"); 5 | 6 | $style.innerHTML = `* { 7 | transition: none !important; 8 | animation: none !important; 9 | } 10 | `; 11 | $doc.body.appendChild($style); 12 | 13 | const bodyStyle = document.createElement("style"); 14 | bodyStyle.innerHTML = ` 15 | .Pane.vertical.Pane1 { 16 | opacity: 0; 17 | } 18 | `; 19 | 20 | document.body.appendChild(bodyStyle); 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /cypress/integration/OrderbookView.spec.ts: -------------------------------------------------------------------------------- 1 | import { WalletConnection } from '../pages/WalletConnection'; 2 | import { cypressVisitWithWeb3, tid } from '../utils'; 3 | import { makeScreenshots } from '../utils/makeScreenshots'; 4 | 5 | describe('Orderbook view ', () => { 6 | 7 | beforeEach(() => { 8 | cypressVisitWithWeb3(); 9 | WalletConnection.connect(); 10 | }); 11 | 12 | it('should render depth chart in the panel', () => { 13 | cy.wait(3000); 14 | cy.get(tid('orderbook-type-list')).click(); 15 | 16 | makeScreenshots('depth-chart', ['macbook-15']); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /cypress/pages/TradeData.ts: -------------------------------------------------------------------------------- 1 | import { tid, timeout } from '../utils'; 2 | 3 | export class TradeData { 4 | public static expectPriceOf(expected: string | RegExp) { 5 | cy.get(tid('trade-price', tid('value')), timeout()).contains(expected); 6 | } 7 | 8 | public static expectPriceImpact(expected: string | RegExp) { 9 | cy.get(tid('trade-price-impact', tid('value')), timeout()).contains(expected); 10 | } 11 | 12 | public static expectSlippageLimit(expected: string | RegExp) { 13 | cy.get(tid('trade-slippage-limit', tid('value')), timeout()).contains(expected); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.circleci/install-yarn.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | 5 | source ~/.bashrc 6 | 7 | # setup node version 8 | nvm install 8 9 | nvm use 8 10 | 11 | nvm alias default 8 12 | 13 | npm install --global yarn 14 | 15 | # Used when we have `apt-get` calls. there is a lock conflict 16 | # and this code allows to way for the lock to be release before going any further 17 | # 18 | # date 19 | # echo -n "Waiting for other software managers to finish..." 20 | # while pgrep apt-get >/dev/null 2>&1 ; do 21 | # echo -n "." 22 | # sleep 5 23 | # done 24 | # echo "" 25 | # date 26 | -------------------------------------------------------------------------------- /src/transactionNotifier/transactionNotifier.ts: -------------------------------------------------------------------------------- 1 | import { combineLatest, Observable } from 'rxjs'; 2 | import { map } from 'rxjs/operators'; 3 | 4 | import { NetworkConfig } from '../blockchain/config'; 5 | import { TxState } from '../blockchain/transactions'; 6 | 7 | export function createTransactionNotifier$( 8 | transactions$: Observable, 9 | interval$: Observable, 10 | context$: Observable, 11 | ) { 12 | return combineLatest(transactions$, context$, interval$).pipe( 13 | map(([transactions, { etherscan }]) => ({ transactions, etherscan })) 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // this file is mostly for IDE to pick it up. It combines tsconfig.prod with tsconfig.test 2 | // to validate setup you should run tsc with tsconfig.prod and tsconfig.test separetely 3 | // splitting config like this ensures, for example, that you won't use JEST in production files (same with cypress) 4 | { 5 | "extends": "./tsconfig.compiler", 6 | "compilerOptions": { 7 | "types": ["jest"] 8 | }, 9 | "include": [ 10 | "*.d.ts", 11 | "src/**/*.d.ts", 12 | "src/**/*.ts", 13 | "src/**/*.tsx", 14 | "./node_modules/web3-typescript-typings/index.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/exchange/orderbook/depth-chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/blockchain/utils.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | 3 | import { tokens } from './config'; 4 | 5 | export function amountFromWei(amount: BigNumber, token: string): BigNumber { 6 | return amount.div(new BigNumber(10).pow(tokens[token].precision)); 7 | } 8 | 9 | export function amountToWei(amount: BigNumber, token: string): BigNumber { 10 | const precision = tokens[token].precision; 11 | return amount.times(new BigNumber(10).pow(precision)); 12 | } 13 | 14 | export const padLeft = (string: string, chars: number, sign?: string) => 15 | Array(chars - string.length + 1).join(sign ? sign : '0') + string; 16 | -------------------------------------------------------------------------------- /src/exchange/priceChart/PriceChartView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const fallBar: string; 2 | export const fallStick: string; 3 | export const riseStick: string; 4 | export const riseBar: string; 5 | export const equal: string; 6 | export const volume: string; 7 | export const bands: string; 8 | export const hoved: string; 9 | export const axisYMainLabel: string; 10 | export const axisYVolumeLabel: string; 11 | export const axisXMainLabel: string; 12 | export const axisLineMain: string; 13 | export const axisYVolumeLineMain: string; 14 | export const axisLineGrid: string; 15 | export const infoBox: string; 16 | export const infoBoxItem: string; 17 | -------------------------------------------------------------------------------- /src/instant/details/TradeSummary.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | @import "../../utils/Mixins"; 3 | 4 | .details { 5 | margin-top: 0; 6 | padding: 1rem 1.5rem; 7 | font-size: .75rem; 8 | 9 | @include media-breakpoint-down(md) { 10 | position: relative; 11 | padding-bottom: 3rem; 12 | } 13 | } 14 | 15 | .summary { 16 | line-height: 1rem; 17 | color: $grey-lighter; 18 | font-weight: 400; 19 | letter-spacing: .8px; 20 | word-break: break-word; 21 | 22 | @include media-breakpoint-down(md) { 23 | font-size: .625rem; 24 | } 25 | } 26 | 27 | .highlight { 28 | color: white; 29 | font-weight: 500; 30 | } -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { initScreenshot, withScreenshot } from 'storybook-chrome-screenshot'; 2 | import { configure, addDecorator } from '@storybook/react'; 3 | 4 | addDecorator(initScreenshot()); 5 | addDecorator( 6 | withScreenshot({ 7 | delay: 1000, 8 | viewport: [ 9 | { 10 | width: 1200, 11 | height: 800, 12 | }, 13 | ], 14 | }), 15 | ); 16 | 17 | // automatically import all files ending in *.stories.js 18 | const req = require.context('../src', true, /.stories.tsx$/); 19 | function loadStories() { 20 | req.keys().forEach(filename => req(filename)); 21 | } 22 | 23 | configure(loadStories, module); 24 | -------------------------------------------------------------------------------- /src/utils/forms/Buttons.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const button: string; 2 | export const disabled: string; 3 | export const buttonGroup: string; 4 | export const grey: string; 5 | export const whiteOutlined: string; 6 | export const green: string; 7 | export const red: string; 8 | export const white: string; 9 | export const greyWhite: string; 10 | export const xs: string; 11 | export const sm: string; 12 | export const md: string; 13 | export const lg: string; 14 | export const unsized: string; 15 | export const full: string; 16 | export const block: string; 17 | export const actionButton: string; 18 | export const close: string; 19 | export const darkRed: string; 20 | -------------------------------------------------------------------------------- /src/icons/coins/eth-color-inverse.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /src/instant/details/TradeDetails.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Mixins"; 2 | @import "../../utils/Variables"; 3 | @import "../Instant"; 4 | 5 | .details { 6 | display: flex; 7 | flex-wrap: wrap; 8 | justify-content: space-between; 9 | width: 100%; 10 | min-height: 4.25rem; 11 | border: 1px solid $button-border-disabled; 12 | border-radius: .25rem; 13 | 14 | margin-top: 1.25rem; 15 | margin-bottom: 1rem; 16 | 17 | padding: .75rem 1rem; 18 | 19 | @include media-breakpoint-down(md) { 20 | min-height: 98px; 21 | } 22 | 23 | &.errors { 24 | @extend %errors-base; 25 | color: $errors; 26 | justify-content: center; 27 | } 28 | } -------------------------------------------------------------------------------- /src/icons/coins/dai-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /src/utils/forms/Buttons.mixins.scss: -------------------------------------------------------------------------------- 1 | @import "../Mixins"; 2 | @import "../Variables"; 3 | 4 | @mixin button($map) { 5 | 6 | color: map-get($map, color); 7 | border-color: map-get($map, border-color); 8 | background-color: map-get($map, bg); 9 | 10 | &:hover:enabled:not(.disabled), 11 | &:focus:enabled:not(.disabled), 12 | &:active:enabled:not(.disabled) { 13 | color: map-get($map, color-hover); 14 | background-color: map-get($map, bg-hover); 15 | border-color: map-get($map, border-color-hover); 16 | } 17 | 18 | &[disabled]:hover, 19 | &[disabled], 20 | &.disabled:hover, 21 | &.disabled { 22 | opacity: 0.5; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/layout/LayoutHelpers.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | 3 | // ---------border box -------------- 4 | .borderBox { 5 | border: 1px solid $grey-light; 6 | border-radius: $border-radius; 7 | } 8 | 9 | .smPadding { 10 | padding: 10px 12px; 11 | } 12 | 13 | .mdPadding { 14 | padding: 14px; 15 | } 16 | 17 | .nonePadding { 18 | padding: 0; 19 | } 20 | 21 | // ------- hr ---------------- 22 | .hr { 23 | border: none; 24 | border-bottom: 1px solid; 25 | clear: both; 26 | width: 100%; 27 | margin: 0; 28 | } 29 | 30 | .lightHr { 31 | border-color: rgba($white, 0.2); 32 | } 33 | 34 | .darkHr { 35 | border-color: $grey-dark; 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/ServerUnreachable.scss: -------------------------------------------------------------------------------- 1 | @import "../Mixins"; 2 | 3 | .block { 4 | @include fontMontserratMedium; 5 | 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: center; 10 | width: 100%; 11 | height: 100%; 12 | flex-grow: 1; 13 | } 14 | 15 | .inline { 16 | display: inline-flex; 17 | align-items: center; 18 | } 19 | 20 | // block version elements 21 | .icon { 22 | margin-right: 0.5em; 23 | } 24 | 25 | .mainInfo { 26 | display: flex; 27 | align-items: center; 28 | } 29 | 30 | .annotate { 31 | margin-top: 0.8em; 32 | font-size: 14px; 33 | } 34 | 35 | .link { 36 | color: white; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/text/Text.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | @import "../Mixins"; 3 | 4 | .muted { 5 | color: $grey-lightest; 6 | } 7 | 8 | .infoLabel { 9 | color: $grey-lightest; 10 | text-transform: capitalize; 11 | letter-spacing: 0.43px; 12 | font-size: 12px; 13 | font-weight: 600; 14 | } 15 | 16 | .buySellSpan { 17 | @include ellipsis; 18 | display: block; 19 | width: 100%; 20 | text-transform: capitalize; 21 | } 22 | 23 | .redSpan { 24 | color: $red; 25 | } 26 | 27 | .greenSpan { 28 | color: $green; 29 | } 30 | 31 | .currency { 32 | @include fontMontserratMedium; 33 | white-space: nowrap; 34 | 35 | &.highlight { 36 | font-weight: 600; 37 | } 38 | } -------------------------------------------------------------------------------- /src/icons/account.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | -------------------------------------------------------------------------------- /src/icons/coins/dgd-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/icons/providers/ledger.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/utils/authorizable.ts: -------------------------------------------------------------------------------- 1 | import { Observable, of } from 'rxjs'; 2 | import { map, switchMap } from 'rxjs/operators'; 3 | 4 | import { User, user$ } from '../blockchain/user'; 5 | 6 | export interface Authorizable { 7 | authorized: boolean; 8 | value?: T; 9 | user?: User; 10 | } 11 | 12 | export function authorizablify( 13 | factory: (u: User) => Observable, 14 | authorize: (u: User) => boolean = user => !!user.account, 15 | ): Observable> { 16 | return user$.pipe( 17 | switchMap(user => user && authorize(user) ? 18 | factory(user).pipe(map(value => ({ value, user, authorized: true }))) : 19 | of({ user, authorized: false }) 20 | ), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/icons/providers/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/icons/providers/parity-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/coins/dai-inverse.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/utils/Common.scss: -------------------------------------------------------------------------------- 1 | @import './Mixins'; 2 | @import './Variables'; 3 | 4 | :global(.hide-all) { 5 | display: none !important; 6 | } 7 | 8 | :global(.hide-md) { 9 | @include media-breakpoint-down(md) { 10 | display: none !important; 11 | } 12 | } 13 | 14 | :global(.hide-lg) { 15 | @include media-breakpoint-down(lg) { 16 | display: none !important; 17 | } 18 | } 19 | 20 | :global(::placeholder) { 21 | color: $grey-light; 22 | } 23 | 24 | :global(.bold) { 25 | font-weight: 700 !important; 26 | } 27 | 28 | :global(.semi-bold) { 29 | font-weight: 600 !important; 30 | } 31 | 32 | :global(.medium) { 33 | font-weight: 500 !important; 34 | } 35 | 36 | :global(.danger) { 37 | color: $errors; 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/icons/utils.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface SvgImageProps extends React.HTMLAttributes { 4 | image: string; 5 | } 6 | 7 | export function SvgImage({ image, ...props }: SvgImageProps) { 8 | return
; 9 | } 10 | 11 | export function SvgImageSimple(image: string) { 12 | return ; 13 | } 14 | 15 | export function loadDataUrl(dataUrl: string): string { 16 | const a = dataUrl.match(/^data:.*?;base64,(.*)$/); 17 | if (!a) { 18 | throw new Error(`malformed data url: ${dataUrl.substr(0, 30)} ...`); 19 | } 20 | return atob(a[1]).replace(/^\<\?xml.*?\?>\n?/, ''); 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/Authorization.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { User } from '../../blockchain/user'; 4 | import { Authorizable } from '../authorizable'; 5 | import { LoggedOut } from '../loadingIndicator/LoggedOut'; 6 | import { Gate } from './Gate'; 7 | 8 | interface AuthorizationProps { 9 | authorizable: Authorizable; 10 | children: (t: T, user: User) => React.ReactElement; 11 | view: string; 12 | } 13 | 14 | export function Authorization({ authorizable, children, view }: AuthorizationProps) { 15 | return }> 16 | { children(authorizable.value as T, authorizable.user as User) } 17 | ; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/text/TransactionStateDescription.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ProgressStage } from '../form'; 3 | import { Timer } from '../Timer'; 4 | 5 | export const TransactionStateDescription = ({ progress }: 6 | { progress: ProgressStage } | any) => { 7 | return ( 8 | {progress === ProgressStage.waitingForApproval && 'Sign on Client'} 9 | {progress === ProgressStage.waitingForConfirmation 10 | && Unconfirmed } 11 | {progress === ProgressStage.done && 'Confirmed'} 12 | {progress === ProgressStage.canceled && 'Cancelled'} 13 | {progress === ProgressStage.fiasco && 'Failure'} 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /cypress/integration/Balances.spec.ts: -------------------------------------------------------------------------------- 1 | import { Balance } from '../pages/Balance'; 2 | import { Tab } from '../pages/Tab'; 3 | import { WalletConnection } from '../pages/WalletConnection'; 4 | import { cypressVisitWithWeb3 } from '../utils/index'; 5 | import { makeScreenshots } from '../utils/makeScreenshots'; 6 | 7 | describe('Balances', () => { 8 | 9 | beforeEach(() => { 10 | cypressVisitWithWeb3(); 11 | WalletConnection.connect(); 12 | }); 13 | 14 | it('should display all token balances', () => { 15 | Tab.balances(); 16 | 17 | Balance.of('ETH').shouldBe(/8,999.../); 18 | Balance.of('WETH').shouldBe(/1,001.../); 19 | Balance.of('DAI').shouldBe(/170.../); 20 | 21 | makeScreenshots('balances'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/icons/providers/web-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /cypress/pages/Proxy.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils'; 2 | 3 | export enum ProxyStatus { 4 | ENABLED = 'connected', 5 | MISSING = 'missing' 6 | } 7 | 8 | export const settings = () => cy.get(tid('account-settings')); 9 | 10 | export const hasStatus = (status: ProxyStatus) => { 11 | switch (status) { 12 | case ProxyStatus.MISSING: 13 | cy.get(tid('proxy-status')).contains('Proxy not created'); 14 | break; 15 | case ProxyStatus.ENABLED: 16 | cy.get(tid('proxy-status')).contains('Proxy available'); 17 | break; 18 | } 19 | }; 20 | 21 | export const create = () => { 22 | cy.get(tid('create-proxy')).click(); 23 | }; 24 | 25 | export const clear = () => cy.window().then((win: any) => { 26 | win.removeProxy(); 27 | }); 28 | -------------------------------------------------------------------------------- /src/icons/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /src/header/Network.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { NetworkConfig } from '../blockchain/config'; 3 | import { Tooltip } from '../utils/tooltip/Tooltip'; 4 | import * as styles from './Header.scss'; 5 | 6 | const Networks = { 7 | kovan: 'Kovan', 8 | main: 'Main' 9 | }; 10 | 11 | export class Network extends React.Component { 12 | 13 | public render() { 14 | const network = this.props.name as 'kovan' || 'main'; 15 | const id = 'status'; 16 | return ( 17 | 18 | 22 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/blockchain/etherscan.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface EtherscanConfig { 3 | url: string; 4 | } 5 | 6 | interface Etherscan { 7 | transaction: (tx: string) => Resource; 8 | } 9 | 10 | interface Resource { 11 | url: string; 12 | open: () => void; 13 | } 14 | 15 | function openPopup(url: string) { 16 | window.open(url, '_blank'); 17 | window.focus(); 18 | } 19 | 20 | function resource(route: (id: string) => string): (id: string) => Resource { 21 | return (id: string) => { 22 | const url = route(id); 23 | const open = () => openPopup(url); 24 | return { url, open }; 25 | }; 26 | } 27 | 28 | export function etherscan(config: EtherscanConfig): Etherscan { 29 | return { 30 | transaction: resource((tx: string) => `${config.url}/tx/${tx}`), 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["typestrict", "tslint:recommended", "tslint-react", "tslint-config-prettier", "tslint-config-airbnb"], 3 | "linterOptions": { 4 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 5 | }, 6 | "rules": { 7 | "jsx-no-lambda": false, 8 | "no-parameter-reassignment": false, 9 | "function-name": false, 10 | "variable-name": false, 11 | "trailing-comma": false, 12 | "interface-name": [true, "never-prefix"], 13 | "object-literal-sort-keys": false, 14 | "no-console": false, 15 | "no-debugger": false, 16 | "max-classes-per-file": false, 17 | "ter-arrow-parens": false, 18 | "no-default-export": true, 19 | "no-var-requires": false, 20 | "prefer-array-literal": [true, { "allow-type-parameters": true }] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config/buildInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | const webpack = require('webpack'); 5 | const pkg = require('../package.json'); 6 | const execa = require('execa'); 7 | 8 | const gitHash = execa.sync('git', ['rev-parse', '--short', 'HEAD']).stdout; 9 | const gitNumCommits = Number(execa.sync('git', ['rev-list', 'HEAD', '--count']).stdout); 10 | const gitDirty = execa.sync('git', ['status', '-s', '-uall']).stdout.length > 0; 11 | const branch = execa.sync('git', ['rev-parse', '--abbrev-ref', 'HEAD']).stdout; 12 | 13 | const buildInfo = new webpack.EnvironmentPlugin({ 14 | __BRANCH__: branch, 15 | __DATE__: Date.now(), 16 | __DIRTY__: gitDirty, 17 | __HASH__: gitHash, 18 | __NAME__: pkg.name, 19 | __COMMITS__: gitNumCommits, 20 | __VERSION__: pkg.version, 21 | }); 22 | 23 | module.exports = buildInfo; -------------------------------------------------------------------------------- /src/wrapUnwrap/WrapUnwrapFormView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const modal: string; 2 | export const modalChild: string; 3 | export const modalOverlay: string; 4 | export const checklistLine: string; 5 | export const checklistTitle: string; 6 | export const checklistSummary: string; 7 | export const checklistHrMargin: string; 8 | export const infoRow: string; 9 | export const infoRowMargin: string; 10 | export const hrMargin: string; 11 | export const summary: string; 12 | export const warning: string; 13 | export const warningIcon: string; 14 | export const warningText: string; 15 | export const buttons: string; 16 | export const btn: string; 17 | export const inputHeader: string; 18 | export const inputTail: string; 19 | export const inputCurrencyAddon: string; 20 | export const panel: string; 21 | export const panelBody: string; 22 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/LoggedOut.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import radioSvg from '../../icons/radio.svg'; 5 | import { SvgImage } from '../icons/utils'; 6 | import { Muted } from '../text/Text'; 7 | import * as styles from './LoggedOut.scss'; 8 | 9 | export const LoggedOut = ({ view = 'the data', className, ...props }: { 10 | view?: string, 11 | className?: string, 12 | }) => { 13 | return ( 14 |
18 |
19 | 20 |
21 | 22 | Connect to view {view} 23 | 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/icons/coins/eth-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 12 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /src/instant/views/AssetSelectorView.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | @import "../../utils/Mixins"; 3 | 4 | .assetsContainer { 5 | display: flex; 6 | margin: auto; 7 | } 8 | 9 | .assets { 10 | display: flex; 11 | justify-content: flex-start; 12 | max-height: 300px; 13 | overflow: auto; 14 | } 15 | 16 | .list { 17 | display: flex; 18 | align-items: center; 19 | justify-content: flex-start; 20 | list-style-type: none; 21 | padding: 0; 22 | margin: auto 0; 23 | flex-wrap: wrap; 24 | max-width: 404px; 25 | 26 | @include media-breakpoint-down(md) { 27 | flex-wrap: wrap; 28 | max-width: 264px; 29 | } 30 | 31 | @include media-breakpoint-down(sm) { 32 | max-width: 210px; 33 | justify-content: center; 34 | } 35 | } 36 | 37 | .list-item { 38 | width: 7.75rem; 39 | margin: .25rem; 40 | } -------------------------------------------------------------------------------- /cypress/pages/WrapUnwrap.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | export const wrapping = (amount: string) => { 4 | cy.get(tid('open-wrap-form'), { timeout: 5000 }).click(); 5 | type(amount); 6 | return Assertion; 7 | }; 8 | 9 | export const unwrapping = (amount: string) => { 10 | cy.get(tid('open-unwrap-form'), { timeout: 5000 }).click(); 11 | type(amount); 12 | return Assertion; 13 | }; 14 | 15 | const type = (amount: string) => { 16 | cy.get(tid('type-amount'), { timeout: 2000 }).type(amount); 17 | }; 18 | 19 | class Assertion { 20 | 21 | public static shouldProceed() { 22 | cy.get(tid('proceed')).click(); 23 | } 24 | 25 | public static shouldFailWith(message: string) { 26 | cy.get(tid('error-msg'), { timeout: 2000 }).contains(message); 27 | cy.get(tid('proceed')).should('be.disabled'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/instant/details/TxStatusRow.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | import * as styles from './TxStatusRow.scss'; 4 | 5 | interface TxStatusRowProps { 6 | icon?: React.ReactNode | HTMLElement; 7 | label: string | React.ReactNode; 8 | status?: string | React.ReactNode; 9 | } 10 | 11 | export class TxStatusRow extends React.Component { 12 | public render() { 13 | const { icon, label, status, ...rest } = this.props; 14 | return ( 15 |
16 | {icon && {icon}} 17 | {label} 18 | {status && {status}} 19 |
20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/forms/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import classnames from 'classnames'; 4 | import * as styles from './Checkbox.scss'; 5 | 6 | type CheckboxProps = 7 | React.HTMLAttributes & { 8 | name: string, 9 | value?: string | string[] | number; 10 | checked?: boolean; 11 | disabled?: boolean; 12 | dataTestId?: string 13 | }; 14 | 15 | export const Checkbox = (props: CheckboxProps) => { 16 | const { children, className, dataTestId, ...otherProps } = props; 17 | 18 | return ( 19 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/forms/Slider.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { Slider } from './Slider'; 4 | 5 | const stories = storiesOf('Form inputs and buttons', module); 6 | 7 | stories.add('Slider', () => ( 8 |
9 |

Default

10 | 11 |

With moveOnHover

12 | 13 |

Disabled

14 | 17 |

Default in progress

18 | 19 |
20 | )); 21 | -------------------------------------------------------------------------------- /src/utils/icons/SocialIcons.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import gitHubSvg from '../../icons/social/git-hub.svg'; 4 | import redditSvg from '../../icons/social/reddit.svg'; 5 | import rocketChatSvg from '../../icons/social/rocket-chat.svg'; 6 | import { socialIcon } from './Icons.scss'; 7 | import { SvgImage } from './utils'; 8 | 9 | export class RocketChat extends React.PureComponent { 10 | public render() { 11 | return ; 12 | } 13 | } 14 | 15 | export class Github extends React.PureComponent { 16 | public render() { 17 | return ; 18 | } 19 | } 20 | 21 | export class Reddit extends React.PureComponent { 22 | public render() { 23 | return ; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/icons/cog-wheel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /src/utils/tabs/Tabs.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | 3 | .Tabs { 4 | margin: 0 10px; 5 | 6 | text-align: center; 7 | padding: 0; 8 | list-style: none; 9 | display: flex; 10 | 11 | li { 12 | flex-grow: 1; 13 | } 14 | } 15 | 16 | 17 | .button { 18 | //width: 100%; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | height: 48px; 23 | background-color: $grey-dark; 24 | border: 1px solid $grey-darken-darkest; 25 | border-radius: $border-radius/2; 26 | color: $font-color-base; 27 | cursor: pointer; 28 | font-size: $font-size-base; 29 | font-weight: 600; 30 | text-transform: uppercase; 31 | 32 | &:hover { 33 | background-color: $grey-darkest; 34 | } 35 | 36 | &:global(.active) { 37 | background-color: $green; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/wrapUnwrap/__snapshots__/wrapUnwrapForm.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Unwrapping initial state 1`] = ` 4 | Object { 5 | "cancel": [Function], 6 | "change": [Function], 7 | "ethBalance": "1000", 8 | "etherPriceUsd": "1", 9 | "gasEstimationStatus": "unset", 10 | "gasPrice": "0.01", 11 | "kind": "unwrap", 12 | "messages": Array [], 13 | "proceed": [Function], 14 | "readyToProceed": undefined, 15 | "wethBalance": "1000", 16 | } 17 | `; 18 | 19 | exports[`Wrapping initial state 1`] = ` 20 | Object { 21 | "cancel": [Function], 22 | "change": [Function], 23 | "ethBalance": "1000", 24 | "etherPriceUsd": "1", 25 | "gasEstimationStatus": "unset", 26 | "gasPrice": "0.01", 27 | "kind": "wrap", 28 | "messages": Array [], 29 | "proceed": [Function], 30 | "readyToProceed": undefined, 31 | "wethBalance": "1000", 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /src/exchange/offerMake/OfferMakeForm.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const ownedResourcesInfoBox: string; 2 | export const noResourcesInfoBox: string; 3 | export const btnGroup: string; 4 | export const btn: string; 5 | export const balanceBtn: string; 6 | export const summary: string; 7 | export const confirmButton: string; 8 | export const hrMargin: string; 9 | export const inputHeader: string; 10 | export const inputTail: string; 11 | export const input: string; 12 | export const inputCurrencyAddon: string; 13 | export const inputPercentAddon: string; 14 | export const errors: string; 15 | export const directSummary: string; 16 | export const directSummaryRight: string; 17 | export const directSummaryLeft: string; 18 | export const directSummaryHorizontal: string; 19 | export const picker: string; 20 | export const pickerBody: string; 21 | export const pickerOrderType: string; 22 | export const pickerDescription: string; 23 | -------------------------------------------------------------------------------- /src/balances/BalancesView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { theAppContext } from '../AppContext'; 4 | import { FlexLayoutRow } from '../utils/layout/FlexLayoutRow'; 5 | 6 | export class BalancesView extends React.Component<{}> { 7 | public render() { 8 | return ( 9 |
10 | 11 | { ({ 12 | AssetOverviewViewRxTx, 13 | TaxExporterTxRx 14 | }) => 15 |
16 | 17 | 18 | 19 | {process.env.REACT_APP_TAX_EXPORTER_ENABLED === '1' && 20 | 21 | 22 | } 23 |
24 | } 25 |
26 |
27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/panel/TopRightCorner.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | import * as styles from './Panel.scss'; 4 | 5 | /* 6 | * This component is used to place icons / button / indicators in the top right corner. 7 | * */ 8 | export class TopRightCorner extends React.Component { 9 | public render() { 10 | const className = this.props.className; 11 | 12 | return ( 13 |
14 | {this.props.children} 15 |
16 | ); 17 | } 18 | } 19 | 20 | export class TopLeftCorner extends React.Component { 21 | public render() { 22 | const className = this.props.className; 23 | 24 | return ( 25 |
26 | {this.props.children} 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/instant/details/TxStatusRow.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Mixins"; 2 | 3 | .txStatusRow { 4 | display: flex; 5 | align-items: center; 6 | width: 100%; 7 | font-size: 0.75rem; 8 | margin-bottom: 1rem; 9 | 10 | @include media-breakpoint-down(md) { 11 | flex-wrap: wrap; 12 | justify-content: center; 13 | text-align: center; 14 | } 15 | 16 | &:last-child { 17 | margin-bottom: 0; 18 | } 19 | 20 | .icon { 21 | height: 1.5rem; 22 | width: 1.5rem; 23 | margin-right: 1rem; 24 | flex-shrink: 0; 25 | } 26 | 27 | .label { 28 | min-width: 9rem; 29 | font-size: 0.75rem; 30 | letter-spacing: 0.7px; 31 | } 32 | 33 | .status { 34 | margin-left: auto; 35 | letter-spacing: .5px; 36 | 37 | @include media-breakpoint-down(md) { 38 | display: flex; 39 | margin: 1rem auto; 40 | position: absolute; 41 | bottom: 0; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/instant/Instant.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const panel: string; 2 | export const header: string; 3 | export const footer: string; 4 | export const tradeDetails: string; 5 | export const details: string; 6 | export const finalization: string; 7 | export const transaction: string; 8 | export const account: string; 9 | export const assets: string; 10 | export const errors: string; 11 | export const warnings: string; 12 | export const hidden: string; 13 | export const tradingSide: string; 14 | export const swapIcon: string; 15 | export const cornerIcon: string; 16 | export const accountIcon: string; 17 | export const settingsIcon: string; 18 | export const backIcon: string; 19 | export const input: string; 20 | export const inputWrapper: string; 21 | export const inputApprox: string; 22 | export const inputPercentage: string; 23 | export const button: string; 24 | export const tradingAsset: string; 25 | export const closeButton: string; 26 | -------------------------------------------------------------------------------- /cypress/pages/Summary.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils'; 2 | 3 | export class Summary { 4 | 5 | public expectProxyBeingCreated = () => { 6 | cy.get(tid('has-proxy')).contains('You successfully created your Proxy!'); 7 | } 8 | 9 | public expectProxyNotBeingCreated = () => { 10 | cy.get(tid('has-proxy')) 11 | .should('not.exist'); 12 | } 13 | 14 | public expectSold = (amount: string, token: string) => { 15 | cy.get(tid('sold-token', tid('amount'))).contains(`${amount}`); 16 | cy.get(tid('sold-token', tid('currency'))).contains(`${token}`); 17 | } 18 | 19 | public expectBought = (amount: string, token: string) => { 20 | cy.get(tid('bought-token', tid('amount'))).contains(`${amount}`); 21 | cy.get(tid('bought-token', tid('currency'))).contains(`${token}`); 22 | } 23 | 24 | public expectPriceOf = (price: string | RegExp) => 25 | cy.get(tid('final-price')).contains(price) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/forms/Radio.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/react'; 2 | import * as React from 'react'; 3 | import { Radio } from './Radio'; 4 | 5 | const stories = storiesOf('Form inputs and buttons', module); 6 | 7 | stories.add('Radio', () => ( 8 |
9 | 12 | Label for radio button 13 | 14 | 18 | Label b 19 | 20 | 25 | Label checked 26 | 27 | 32 | Label disabled 33 | 34 | 38 | Label e 39 | 40 |
41 | )); 42 | -------------------------------------------------------------------------------- /src/instant/details/TradeDetails.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const panel: string; 2 | export const header: string; 3 | export const footer: string; 4 | export const tradeDetails: string; 5 | export const details: string; 6 | export const finalization: string; 7 | export const transaction: string; 8 | export const account: string; 9 | export const assets: string; 10 | export const errors: string; 11 | export const warnings: string; 12 | export const hidden: string; 13 | export const tradingSide: string; 14 | export const swapIcon: string; 15 | export const cornerIcon: string; 16 | export const accountIcon: string; 17 | export const settingsIcon: string; 18 | export const backIcon: string; 19 | export const input: string; 20 | export const inputWrapper: string; 21 | export const inputApprox: string; 22 | export const inputPercentage: string; 23 | export const button: string; 24 | export const tradingAsset: string; 25 | export const closeButton: string; 26 | -------------------------------------------------------------------------------- /src/icons/providers/status-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'unset') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI, in coverage mode, or explicitly running all tests 22 | if ( 23 | !process.env.CI && 24 | argv.indexOf('--coverage') === -1 && 25 | argv.indexOf('--watchAll') === -1 26 | ) { 27 | argv.push('--watch'); 28 | } 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /src/exchange/offerMake/OfferMakePanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Loadable } from '../../utils/loadable'; 4 | import { LoadingIndicator } from '../../utils/loadingIndicator/LoadingIndicator'; 5 | import { PanelHeader } from '../../utils/panel/Panel'; 6 | import { OfferFormState } from './offerMake'; 7 | import { OfferMakeForm } from './OfferMakeForm'; 8 | import * as styles from './OfferMakePanel.scss'; 9 | 10 | export class OfferMakePanel extends React.Component> { 11 | 12 | public render() { 13 | if (this.props.status === 'loaded') { 14 | const formState = this.props.value as OfferFormState; 15 | return (); 16 | } 17 | 18 | return (<> 19 | Create order 20 |
21 | 22 |
23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /src/instant/details/TradeData.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Mixins"; 2 | @import "../../utils/Variables"; 3 | 4 | .entry { 5 | display: flex; 6 | width: 100%; 7 | max-width: 12rem; 8 | justify-content: space-between; 9 | align-items: center; 10 | 11 | &.reversed { 12 | .label { 13 | color: rgba(255, 255, 255, 0.90) 14 | } 15 | 16 | .value { 17 | color: $dark-gray-blue; 18 | } 19 | 20 | .infoIcon { 21 | color: rgba(255, 255, 255, 0.90); 22 | border-color: rgba(255, 255, 255, 0.90); 23 | } 24 | } 25 | 26 | @include media-breakpoint-down(md) { 27 | max-width: none; 28 | } 29 | } 30 | 31 | .label { 32 | @include fontMontserratMedium; 33 | font-size: 12px; 34 | color: $dark-gray-blue; 35 | letter-spacing: 1.1px; 36 | } 37 | 38 | .value { 39 | @include fontMontserratMedium; 40 | font-size: 12px; 41 | color: rgba(255, 255, 255, 0.90); 42 | letter-spacing: 1px; 43 | text-align: right; 44 | } 45 | -------------------------------------------------------------------------------- /src/instant/CurrentPrice.tsx: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import classnames from 'classnames'; 3 | import * as React from 'react'; 4 | import { Approximate } from '../utils/Approximate'; 5 | import { formatAmount } from '../utils/formatters/format'; 6 | import * as styles from './Instant.scss'; 7 | 8 | export class CurrentPrice extends React.Component<{ 9 | price?: BigNumber, 10 | quotation?: string 11 | }> { 12 | public render() { 13 | const { price, quotation } = this.props; 14 | return ( 15 |
16 | Current Estimated Price 17 | 18 | 19 | {price && formatAmount(price, 'USD')} 20 | {quotation ? quotation : ''} 21 | 22 | 23 |
24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/landingPage/Banner.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const button: string; 2 | export const disabled: string; 3 | export const buttonGroup: string; 4 | export const grey: string; 5 | export const whiteOutlined: string; 6 | export const green: string; 7 | export const red: string; 8 | export const white: string; 9 | export const greyWhite: string; 10 | export const xs: string; 11 | export const sm: string; 12 | export const md: string; 13 | export const lg: string; 14 | export const unsized: string; 15 | export const full: string; 16 | export const block: string; 17 | export const actionButton: string; 18 | export const close: string; 19 | export const darkRed: string; 20 | export const section: string; 21 | export const contentPlaceholder: string; 22 | export const content: string; 23 | export const pre: string; 24 | export const panel: string; 25 | export const headline: string; 26 | export const btnPlaceholder: string; 27 | export const btn: string; 28 | export const warningIcon: string; 29 | -------------------------------------------------------------------------------- /src/utils/forms/Radio.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import classnames from 'classnames'; 4 | import * as styles from './Radio.scss'; 5 | 6 | type RadioProps = 7 | React.HTMLAttributes & { 8 | // sizer?: 'sm', 9 | hasError?: boolean, 10 | name: string, 11 | value?: string | string[] | number; 12 | checked?: boolean; 13 | disabled?: boolean; 14 | dataTestId?: string 15 | }; 16 | 17 | export const Radio = (props: RadioProps) => { 18 | const { children, className, hasError, dataTestId, ...otherProps } = props; 19 | 20 | return ( 21 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/balances/TaxExporter.scss: -------------------------------------------------------------------------------- 1 | @import "../utils/Mixins"; 2 | 3 | .taxExporterDescription { 4 | flex-grow: 1; 5 | font-size: 14px; 6 | display: block; 7 | margin-top: 8px; 8 | margin-bottom: 8px; 9 | 10 | @include media-breakpoint-down(sm) { 11 | font-size: 12px; 12 | } 13 | 14 | span { 15 | color: #fff; 16 | display: block; 17 | font-size: 15px; 18 | margin-bottom: 8px; 19 | 20 | @include media-breakpoint-down(sm) { 21 | font-size: 14px; 22 | } 23 | } 24 | 25 | a { 26 | color: #6fc2e9; 27 | } 28 | } 29 | 30 | .taxExporterPanelBody { 31 | display: flex; 32 | width: 100%; 33 | border-top: 1px solid #393942; 34 | } 35 | 36 | .taxExporterButton { 37 | margin-top: 8px; 38 | 39 | @include media-breakpoint-down(sm) { 40 | margin-top: 0; 41 | align-self: center; 42 | } 43 | } 44 | 45 | .progressIcon { 46 | position: relative; 47 | width: 14px; 48 | height: 14px; 49 | top: 2px; 50 | margin: 0 25px; 51 | } -------------------------------------------------------------------------------- /src/icons/providers/metamask-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/instant/asset/Asset.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | @import "../../utils/Mixins"; 3 | 4 | .asset { 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: space-evenly; 8 | align-items: center; 9 | 10 | border: 1px solid $grey-light; 11 | border-radius: .325rem; 12 | 13 | width: 100%; 14 | height: 100px; 15 | 16 | cursor: pointer; 17 | 18 | color: white; 19 | background: transparent; 20 | 21 | @include fontMontserratMedium; 22 | font-size: .75rem; 23 | letter-spacing: 1px; 24 | 25 | &:hover { 26 | border-color: $button-border-hover; 27 | } 28 | 29 | &.locked { 30 | pointer-events: none; 31 | 32 | border-color: $button-border-disabled; 33 | color: $button-border-disabled; 34 | 35 | @include iconsDisabled( 36 | $button-border-disabled, 37 | $grey-darkest 38 | ) 39 | } 40 | } 41 | 42 | .icon { 43 | height: map-get($inputHeight, lg); 44 | width: map-get($inputHeight, lg); 45 | } 46 | -------------------------------------------------------------------------------- /src/exchange/depthChart/DepthChartView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const depthChartWithLoading: string; 2 | export const depthChart: string; 3 | export const sellChart: string; 4 | export const sellChartDark: string; 5 | export const buyChart: string; 6 | export const buyChartDark: string; 7 | export const hidden: string; 8 | export const axisMainLabel: string; 9 | export const axisLineMain: string; 10 | export const axisLineAdditional: string; 11 | export const axisYLineAdditional: string; 12 | export const axisYTokenLabel: string; 13 | export const infoBox: string; 14 | export const infoBoxRed: string; 15 | export const infoBoxGreen: string; 16 | export const sellText: string; 17 | export const buyText: string; 18 | export const infoSummary: string; 19 | export const infoSummaryLine: string; 20 | export const infoSummaryLegendImg: string; 21 | export const legendImg: string; 22 | export const smallLegend: string; 23 | export const dotGlow: string; 24 | export const dotSell: string; 25 | export const dotBuy: string; 26 | -------------------------------------------------------------------------------- /src/footer/Footer.scss: -------------------------------------------------------------------------------- 1 | @import "../utils/Variables"; 2 | 3 | .links { 4 | & > *:after { 5 | content: "/"; 6 | display: inline-block; 7 | margin-left: 0.5em; 8 | margin-right: 0.5em; 9 | color: rgba(255, 255, 255, 0.1); 10 | } 11 | 12 | & > *:last-child:after { 13 | content: ""; 14 | } 15 | } 16 | 17 | .footerSeparator { 18 | margin-top: 4em; 19 | margin-bottom: 2em; 20 | border: 1px solid rgba(255, 255, 255, 0.10); 21 | } 22 | 23 | .footer { 24 | text-align: center; 25 | font-family: $font-main; 26 | font-size: 14px; 27 | color: #B3B3B9; 28 | letter-spacing: 0.2px; 29 | margin-bottom: 2em; 30 | 31 | & > * { 32 | margin-bottom: 16px; 33 | } 34 | 35 | 36 | a { 37 | text-decoration: none; 38 | color: #B3B3B9; 39 | 40 | &:hover{ 41 | color:#FFF; 42 | } 43 | 44 | &.iconLink { 45 | display: inline-block; 46 | margin-right: 0.5em; 47 | height: 16px; 48 | vertical-align: top; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/connect.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Observable, Subscription } from 'rxjs'; 3 | 4 | export function connect( 5 | WrappedComponent: React.ComponentType, 6 | state$: Observable, 7 | dontRenderTooEarly: boolean = true 8 | ): React.ComponentType { 9 | return class extends React.Component { 10 | private subscription!: Subscription; 11 | private loaded: boolean = false; 12 | 13 | public componentWillMount() { 14 | this.subscription = state$.subscribe((v: R) => { 15 | this.loaded = true; 16 | this.setState(v); 17 | }); 18 | } 19 | 20 | public componentWillUnmount() { 21 | this.subscription.unsubscribe(); 22 | } 23 | 24 | public render() { 25 | if (dontRenderTooEarly && !this.loaded) { 26 | return null; 27 | } 28 | return ; 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/icons/coins/eth-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 14 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /src/utils/Timer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface TimerProps { 4 | start: Date; 5 | } 6 | 7 | export class Timer extends React.Component { 8 | private timer: any; 9 | 10 | public constructor(props: TimerProps) { 11 | super(props); 12 | this.state = { 13 | elapsed: 0, 14 | }; 15 | this.tick = this.tick.bind(this); 16 | } 17 | 18 | public componentDidMount() { 19 | this.timer = setInterval(this.tick, 50); 20 | } 21 | 22 | public componentWillUnmount() { 23 | clearInterval(this.timer); 24 | } 25 | 26 | public render() { 27 | const elapsed = Math.round(this.state.elapsed / 1000); 28 | const minutes = Math.floor(elapsed / 60).toFixed(0); 29 | const seconds = (elapsed % 60).toFixed(0).padStart(2, '0'); 30 | 31 | return {minutes}:{seconds}; 32 | } 33 | 34 | private tick() { 35 | this.setState({ 36 | elapsed: new Date().valueOf() - this.props.start.valueOf() 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/loadable.ts: -------------------------------------------------------------------------------- 1 | import { concat, Observable, of } from 'rxjs'; 2 | import { catchError, first, map, skip, startWith } from 'rxjs/operators'; 3 | 4 | import { onEveryBlock$ } from '../blockchain/network'; 5 | import { TradingPair } from '../exchange/tradingPair/tradingPair'; 6 | 7 | export type LoadableStatus = 'loading' | 'loaded' | 'error'; 8 | 9 | export interface Loadable { 10 | status: LoadableStatus; 11 | value?: T; 12 | error?: Error; 13 | } 14 | 15 | export function loadablifyLight(observable: Observable): Observable> { 16 | return observable.pipe( 17 | map(value => ({ value, status: 'loaded' })), 18 | startWith({ status: 'loading' } as Loadable), 19 | catchError((error, source) => { 20 | console.log(error); 21 | return concat( 22 | of({ error, status: 'error' }), 23 | onEveryBlock$.pipe(skip(1), first()), 24 | source, 25 | ); 26 | }) 27 | ); 28 | } 29 | 30 | export type LoadableWithTradingPair = Loadable & {tradingPair: TradingPair}; 31 | -------------------------------------------------------------------------------- /src/utils/price.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { formatPrice } from './formatters/format'; 3 | 4 | export const calculateTradePrice = ( 5 | sellToken: string, 6 | sellAmount: BigNumber, 7 | buyToken: string, 8 | buyAmount: BigNumber 9 | ) => { 10 | return ( 11 | sellToken.toLowerCase() === 'dai' 12 | || (sellToken.toLowerCase() === 'eth' && buyToken.toLowerCase() !== 'dai') 13 | ) 14 | ? 15 | { 16 | price: new BigNumber(formatPrice(sellAmount.div(buyAmount), sellToken)), 17 | quotation: `${buyToken}/${sellToken}` 18 | } 19 | : 20 | { 21 | price: new BigNumber(formatPrice(buyAmount.div(sellAmount), buyToken)), 22 | quotation: `${sellToken}/${buyToken}` 23 | }; 24 | }; 25 | 26 | export const getQuote = (sellToken: string, buyToken: string) => { 27 | return ( 28 | sellToken.toLowerCase() === 'dai' 29 | || (sellToken.toLowerCase() === 'eth' && buyToken.toLowerCase() !== 'dai') 30 | ) 31 | ? `${buyToken}/${sellToken}` 32 | : `${sellToken}/${buyToken}`; 33 | }; 34 | -------------------------------------------------------------------------------- /src/blockchain/web3.ts: -------------------------------------------------------------------------------- 1 | import { from, Observable } from 'rxjs'; 2 | import { map, shareReplay } from 'rxjs/operators'; 3 | import * as Web3 from 'web3'; 4 | 5 | const infuraProjectId = 'd96fcc7c667e4a03abf1cecd266ade2d'; 6 | const infuraUrl = `https://mainnet.infura.io/v3/${infuraProjectId}`; 7 | const ethereum = { 8 | url: infuraUrl, 9 | }; 10 | 11 | export let web3 : Web3; 12 | 13 | export type Web3Status = 'ready' | 'readonly' | 'missing' | 'initializing'; 14 | 15 | export interface Web3Window { 16 | web3?: any; 17 | ethereum?: any; 18 | } 19 | 20 | export const web3Status$: Observable = from(['initializing']).pipe( 21 | map(() => { 22 | const win = window as Web3Window; 23 | if (win.web3) { 24 | web3 = new Web3(win.web3.currentProvider); 25 | return 'ready'; 26 | } 27 | web3 = new Web3(new Web3.providers.HttpProvider(ethereum.url)); 28 | return 'readonly'; 29 | }), 30 | shareReplay(1), 31 | ); 32 | web3Status$.subscribe(); 33 | 34 | export function setupFakeWeb3ForTesting() { 35 | web3 = new Web3(); 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/forms/Select.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import * as styles from './Select.scss'; 5 | 6 | type SelectProps = 7 | React.SelectHTMLAttributes 8 | & { bordered?: boolean, 9 | sizer?: 'sm' | 'md' | 'lg' | 'unsized', 10 | wrapperClassName?: string, 11 | dataTestId?: string, 12 | }; 13 | 14 | export function Select(props: SelectProps) { 15 | const { style, bordered, sizer, className, wrapperClassName, disabled, ...selectProps } = props; 16 | 17 | return
24 | 29 |
; 30 | } 31 | -------------------------------------------------------------------------------- /src/blockchain/abi/proxy-registry.abi.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "constant": false, 3 | "inputs": [], 4 | "name": "build", 5 | "outputs": [{ 6 | "name": "proxy", 7 | "type": "address" 8 | }], 9 | "payable": false, 10 | "stateMutability": "nonpayable", 11 | "type": "function" 12 | }, { 13 | "constant": true, 14 | "inputs": [{ 15 | "name": "", 16 | "type": "address" 17 | }], 18 | "name": "proxies", 19 | "outputs": [{ 20 | "name": "", 21 | "type": "address" 22 | }], 23 | "payable": false, 24 | "stateMutability": "view", 25 | "type": "function" 26 | }, { 27 | "constant": false, 28 | "inputs": [{ 29 | "name": "owner", 30 | "type": "address" 31 | }], 32 | "name": "build", 33 | "outputs": [{ 34 | "name": "proxy", 35 | "type": "address" 36 | }], 37 | "payable": false, 38 | "stateMutability": "nonpayable", 39 | "type": "function" 40 | }, { 41 | "inputs": [{ 42 | "name": "factory_", 43 | "type": "address" 44 | }], 45 | "payable": false, 46 | "stateMutability": "nonpayable", 47 | "type": "constructor" 48 | }] 49 | -------------------------------------------------------------------------------- /src/exchange/tradingPair/TradingPairView.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const container: string; 2 | export const dropdown: string; 3 | export const mobileWrapper: string; 4 | export const dropdownIcon: string; 5 | export const dropdownBtn: string; 6 | export const dropdownBtnDisabled: string; 7 | export const dropdownBtnActive: string; 8 | export const dropdownListWrapper: string; 9 | export const dropdownList: string; 10 | export const dropdownItem: string; 11 | export const dropdownItemLink: string; 12 | export const activePairView: string; 13 | export const activePairViewIcon: string; 14 | export const activePairViewTokenBase: string; 15 | export const activePairViewTokenQuote: string; 16 | export const pairView: string; 17 | export const iconQuote: string; 18 | export const iconBase: string; 19 | export const tokenBase: string; 20 | export const tokenQuote: string; 21 | export const price: string; 22 | export const center: string; 23 | export const priceDiff: string; 24 | export const active: string; 25 | export const pairInfo: string; 26 | export const pairInfoLabel: string; 27 | export const pairInfoValue: string; 28 | -------------------------------------------------------------------------------- /cypress/integration/Allowances.spec.ts: -------------------------------------------------------------------------------- 1 | import { Allowance, ALLOWANCE_STATE } from '../pages/Allowance'; 2 | import { Tab } from '../pages/Tab'; 3 | import { WalletConnection } from '../pages/WalletConnection'; 4 | import { cypressVisitWithWeb3 } from '../utils'; 5 | 6 | describe('Setting allowances', () => { 7 | 8 | beforeEach(() => { 9 | cypressVisitWithWeb3(); 10 | WalletConnection.connect(); 11 | Tab.balances(); 12 | }); 13 | 14 | it('should enable allowance on a given token', () => { 15 | const allowance = Allowance.of('WETH'); 16 | 17 | allowance.shouldBe(ALLOWANCE_STATE.ENABLED); 18 | allowance.toggle(); 19 | 20 | allowance.shouldBe(ALLOWANCE_STATE.DISABLED); 21 | allowance.toggle(); 22 | 23 | allowance.shouldBe(ALLOWANCE_STATE.ENABLED); 24 | }); 25 | 26 | it('should disable allowance on a given token', () => { 27 | const allowance = Allowance.of('DAI'); 28 | 29 | allowance.shouldBe(ALLOWANCE_STATE.ENABLED); 30 | allowance.toggle(); 31 | 32 | allowance.shouldBe(ALLOWANCE_STATE.DISABLED); 33 | allowance.toggle(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/header/WalletConnection.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const button: string; 2 | export const disabled: string; 3 | export const buttonGroup: string; 4 | export const grey: string; 5 | export const whiteOutlined: string; 6 | export const green: string; 7 | export const red: string; 8 | export const white: string; 9 | export const greyWhite: string; 10 | export const xs: string; 11 | export const sm: string; 12 | export const md: string; 13 | export const lg: string; 14 | export const unsized: string; 15 | export const full: string; 16 | export const block: string; 17 | export const actionButton: string; 18 | export const close: string; 19 | export const darkRed: string; 20 | export const section: string; 21 | export const heading: string; 22 | export const list: string; 23 | export const item: string; 24 | export const single: string; 25 | export const wallet: string; 26 | export const icon: string; 27 | export const selected: string; 28 | export const inactive: string; 29 | export const termsAndConditions: string; 30 | export const buttonPlaceholder: string; 31 | export const btn: string; 32 | export const connect: string; 33 | -------------------------------------------------------------------------------- /src/exchange/orderbook/OrderbookView.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | @import "../../utils/Mixins"; 3 | 4 | .orderbook { 5 | width: 452px; 6 | height: 100%; 7 | overflow: hidden; 8 | 9 | @include media-breakpoint-down(lg) { 10 | width: 100%; 11 | } 12 | } 13 | 14 | .switchBtn { 15 | margin-right: 0; // 0.7em; 16 | padding: 0; 17 | width: 30px; 18 | height: 30px; 19 | fill: white; 20 | } 21 | 22 | 23 | .spreadRow { 24 | background-color: rgba(white, 0.02); 25 | } 26 | 27 | .orderbookTable { 28 | max-height: 360px; 29 | @include tableColumnsWidth(146px 146px 146px); 30 | 31 | td, th { 32 | &:last-child { 33 | flex-grow: 1; 34 | } 35 | } 36 | 37 | } 38 | 39 | // ------------------ animations for table ----------------------- 40 | :global(.order-enter) { 41 | opacity: 0.01; 42 | } 43 | 44 | :global(.order-enter-active) { 45 | opacity: 1; 46 | transition: opacity 1000ms ease-in; 47 | } 48 | 49 | :global(.order-exit) { 50 | opacity: 1; 51 | } 52 | 53 | :global(.order-exit-active) { 54 | opacity: 0.01; 55 | transition: opacity 1000ms ease-in; 56 | } 57 | -------------------------------------------------------------------------------- /src/exchange/allTrades/AllTradesView.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Mixins"; 2 | 3 | .allTradesTable { 4 | max-height: 254px; 5 | @include tableColumnsWidth(152px 152px 142px); 6 | 7 | td, th { 8 | // time column 9 | &:last-child { 10 | flex-grow: 1; 11 | white-space: nowrap; 12 | } 13 | } 14 | 15 | .loadMore { 16 | padding-left: 24px; 17 | } 18 | } 19 | 20 | .loader { 21 | display: inline-block; 22 | width: 16px; 23 | min-height: 16px; 24 | margin-right: 4px; 25 | background-size: contain; 26 | background-image: url('../../utils/loadingIndicator/LoadingIndicatorDark.gif'); 27 | background-repeat: no-repeat; 28 | background-position: center; 29 | opacity: 0.5; 30 | } 31 | 32 | // ------------------ animations for table ----------------------- 33 | :global(.trade-enter) { 34 | opacity: 0.01; 35 | } 36 | 37 | :global(.trade-enter-active) { 38 | opacity: 1; 39 | transition: opacity 1000ms ease-in; 40 | } 41 | 42 | :global(.trade-exit) { 43 | opacity: 1; 44 | } 45 | 46 | :global(.trade-exit-active) { 47 | opacity: 0.01; 48 | transition: opacity 1000ms ease-in; 49 | } 50 | -------------------------------------------------------------------------------- /src/instant/views/AllowancesView.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | @import "../../utils/Mixins"; 3 | 4 | .assets { 5 | display: flex; 6 | align-items: center; 7 | justify-content: flex-start; 8 | flex-wrap: wrap; 9 | list-style-type: none; 10 | padding: 0; 11 | margin-top: 1rem; 12 | 13 | @include media-breakpoint-down(md){ 14 | justify-content: center; 15 | } 16 | } 17 | 18 | .asset { 19 | position: relative; 20 | display: inline-flex; 21 | align-items: center; 22 | border-radius: 4px; 23 | padding: 1rem 1.5rem; 24 | margin: .25rem; 25 | font-size: 16px; 26 | font-weight: 500; 27 | letter-spacing: .2px; 28 | width: 100%; 29 | max-width: 212px; 30 | } 31 | 32 | .tokenIcon { 33 | height: 28px; 34 | width: 28px; 35 | margin-right: 12px; 36 | } 37 | 38 | .indicator { 39 | position: absolute; 40 | top: 10px; 41 | right: 10px; 42 | width: 16px; 43 | height: 16px; 44 | } 45 | 46 | .isAllowed { 47 | :global(.done-icon) { 48 | fill: white; 49 | } 50 | } 51 | 52 | .disabled { 53 | :global(.done-icon) { 54 | fill: $yet-another-dark-grayish-blue; 55 | } 56 | } -------------------------------------------------------------------------------- /src/landingPage/client/Client.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import metamaskSvg from '../../icons/clients/metamask.svg'; 4 | import paritySvg from '../../icons/clients/parity.svg'; 5 | import { SvgImage } from '../../utils/icons/utils'; 6 | import * as styles from './Client.scss'; 7 | 8 | type CLIENT_TYPE = 'metamask' | 'parity'; 9 | 10 | interface ClientProps { 11 | client: CLIENT_TYPE; 12 | } 13 | 14 | export class Client extends React.Component { 15 | public render() { 16 | switch (this.props.client){ 17 | case 'metamask': 18 | return ( 19 |
22 | 23 | 24 | ); 25 | case 'parity': 26 | return ( 27 | 30 | 31 | 32 | ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cypress/pages/Allowance.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | export enum ALLOWANCE_STATE { 4 | ENABLED = 'enabled', 5 | DISABLED = 'disabled' 6 | } 7 | 8 | export class Allowance { 9 | public static of = (tokenSymbol: string) => { 10 | const symbol = tokenSymbol.toUpperCase(); 11 | cy.get(tid(`${symbol}-overview`), { timeout: 10000 }).as(`${symbol}`); 12 | 13 | return { 14 | toggle: () => { 15 | cy.get(`@${symbol}`).find(tid('toggle-allowance')).click(); 16 | }, 17 | 18 | shouldBe: (state: ALLOWANCE_STATE) => { 19 | switch (state) { 20 | case ALLOWANCE_STATE.DISABLED: 21 | cy.get(`@${symbol}`) 22 | .find(tid('toggle-button-state'), { timeout: 10000 }) 23 | .should('have.attr', 'data-toggle-state', 'disabled'); 24 | break; 25 | case ALLOWANCE_STATE.ENABLED: 26 | cy.get(`@${symbol}`) 27 | .find(tid('toggle-button-state'), { timeout: 10000 }) 28 | .should('have.attr', 'data-toggle-state', 'enabled'); 29 | break; 30 | } 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cypress/pages/Orderbook.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | class Order { 4 | 5 | public price() { 6 | return cy.get('@order').find(tid('price')); 7 | } 8 | 9 | public amount() { 10 | return cy.get('@order').find(tid('amount')); 11 | } 12 | 13 | public total() { 14 | return cy.get('@order').find(tid('total')); 15 | } 16 | } 17 | 18 | class Orders { 19 | 20 | constructor(public type: OrderType) { 21 | } 22 | 23 | public countIs(number: number) { 24 | cy.get(tid(this.type), { timeout: 60000 }).should('have.length', number); 25 | } 26 | 27 | public first() { 28 | cy.get(tid(this.type)).first().as('order'); 29 | 30 | return new Order(); 31 | } 32 | 33 | public number(number: number) { 34 | cy.get(tid(this.type)).eq(number - 1).as('order'); 35 | 36 | return new Order(); 37 | } 38 | 39 | public last() { 40 | cy.get(tid(this.type)).last().as('order'); 41 | 42 | return new Order(); 43 | } 44 | } 45 | 46 | export enum OrderType { 47 | BUY = 'buy', 48 | SELL = 'sell' 49 | } 50 | 51 | export class Orderbook { 52 | public static list(type: OrderType) { 53 | return new Orders(type); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /codechecks-e2e-vis-reg.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import { codechecks } from "@codechecks/client"; 3 | // tslint:disable-next-line 4 | const exec = require("await-exec") as (cmd: string, opt: any) => Promise; 5 | 6 | export async function main() { 7 | await visReg(); 8 | } 9 | 10 | async function visReg() { 11 | const execOptions = { timeout: 100000, cwd: process.cwd(), log: true }; 12 | await codechecks.saveDirectory("e2e-vis-reg", join(__dirname, "__screenshots__")); 13 | 14 | if (codechecks.isPr()) { 15 | await codechecks.getDirectory("e2e-vis-reg", join(__dirname, ".reg/expected")); 16 | await exec("./node_modules/.bin/reg-suit compare", execOptions); 17 | 18 | await codechecks.saveDirectory("e2e-vis-reg-report", join(__dirname, ".reg")); 19 | 20 | const reportData = require("./.reg/out.json"); 21 | await codechecks.success({ 22 | name: "Visual regression for E2E", 23 | shortDescription: `Changed: ${reportData.failedItems.length}, New: ${ 24 | reportData.newItems.length 25 | }, Deleted: ${reportData.deletedItems.length}`, 26 | detailsUrl: codechecks.getArtifactLink("/e2e-vis-reg-report/index.html"), 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cypress/pages/Trades.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | class Trade { 4 | public price() { 5 | return cy.get('@trade').find(tid('price')); 6 | } 7 | 8 | public amount() { 9 | return cy.get('@trade').find(tid('amount')); 10 | } 11 | 12 | public total() { 13 | return cy.get('@trade').find(tid('total')); 14 | } 15 | 16 | public type() { 17 | return cy.get('@trade').find(tid('type')); 18 | } 19 | 20 | public cancel() { 21 | cy.get('@trade').find(tid('cancel'), { timeout: 10000 }).click(); 22 | } 23 | } 24 | 25 | export class Trades { 26 | 27 | public static countIs(number: number) { 28 | cy.get(tid('my-trades'), { timeout: 15000 }).should('have.length', number); 29 | } 30 | 31 | public static first() { 32 | cy.get(tid('my-trades'), { timeout: 10000 }).first().as('trade'); 33 | 34 | return new Trade(); 35 | } 36 | 37 | public static number(number: number) { 38 | cy.get(tid('my-trades'), { timeout: 10000 }).eq(number - 1).as('trade'); 39 | 40 | return new Trade(); 41 | } 42 | 43 | public static last() { 44 | cy.get(tid('my-trades'), { timeout: 10000 }).as('trade'); 45 | 46 | return new Trade(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path") 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | const wp = require('@cypress/webpack-preprocessor') 16 | module.exports = (on) => { 17 | const options = { 18 | webpackOptions: { 19 | resolve: { 20 | extensions: [".ts", ".tsx", ".js"] 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.tsx?$/, 26 | loader: "ts-loader", 27 | options: { 28 | configFile: join(__dirname, "../tsconfig.json"), 29 | transpileOnly: true 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | }; 36 | on('file:preprocessor', wp(options)) 37 | }; -------------------------------------------------------------------------------- /src/utils/layout/LayoutHelpers.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import * as styles from './LayoutHelpers.scss'; 5 | 6 | type BorderBoxProps = 7 | React.HTMLAttributes & 8 | { 9 | className?: any, 10 | children?: any, 11 | padding?: 'md' | 'sm' | 'none', 12 | }; 13 | 14 | type Padding = 'smPadding' | 'mdPadding' | 'nonePadding'; 15 | 16 | export const BorderBox = ({ className, children, padding, ...props }: BorderBoxProps) => { 17 | const paddingStyle = (padding || 'md') + 'Padding' as Padding; 18 | return ( 19 |
23 | {children} 24 |
25 | ); 26 | }; 27 | 28 | type HrColor = 'lightHr' | 'darkHr'; 29 | 30 | type HrProps = 31 | React.HTMLAttributes & { 32 | color?: 'light' | 'dark', 33 | className?: any, 34 | }; 35 | 36 | export const Hr = ({ color, className, ...props }: HrProps) => { 37 | const colorHr = (color || 'light') + 'Hr' as HrColor; 38 | return (
); 42 | }; 43 | -------------------------------------------------------------------------------- /src/exchange/myTrades/closedTrades.ts: -------------------------------------------------------------------------------- 1 | import { isEqual } from 'lodash'; 2 | import { combineLatest, Observable, of } from 'rxjs'; 3 | import { distinctUntilChanged, exhaustMap, map, shareReplay, switchMap } from 'rxjs/operators'; 4 | 5 | import { NetworkConfig } from '../../blockchain/config'; 6 | import { every10Seconds$ } from '../../blockchain/network'; 7 | import { compareByTimestampOnly, getTrades, Trade } from '../trades'; 8 | import { TradingPair } from '../tradingPair/tradingPair'; 9 | 10 | export function createMyClosedTrades$( 11 | account$: Observable, 12 | context$: Observable, 13 | { base, quote }: TradingPair 14 | ): Observable { 15 | return combineLatest(account$, context$).pipe( 16 | switchMap(([account, context]) => 17 | !(account && context) ? of([]) : 18 | every10Seconds$.pipe( 19 | exhaustMap(() => 20 | getTrades(context, base, quote, 'myTrades', { 21 | account 22 | }).pipe( 23 | map(trades => trades.sort(compareByTimestampOnly)), 24 | ) 25 | ), 26 | distinctUntilChanged(isEqual), 27 | ) 28 | ), 29 | shareReplay(1), 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/tabs/StaticTabs.tsx: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | 3 | import classnames from 'classnames'; 4 | import * as React from 'react'; 5 | 6 | import * as styles from './Tabs.scss'; 7 | 8 | export class StaticTabs extends React.Component { 9 | constructor(props: any) { 10 | super(props); 11 | this.state = { activeTab: props.default }; 12 | } 13 | 14 | public switchTab = (tab: string) => { 15 | return (event: React.MouseEvent): void => { 16 | event.stopPropagation(); 17 | this.setState({ activeTab: tab }); 18 | }; 19 | } 20 | 21 | public render() { 22 | const { tabs } = this.props; 23 | const { activeTab } = this.state as any; 24 | return ( 25 |
26 | 38 | {tabs[activeTab]} 39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/icons/providers/coinbase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/exchange/OrderbookPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; 5 | import { map } from 'rxjs/operators'; 6 | 7 | export enum OrderbookViewKind { 8 | depthChart = 'depthChart', 9 | list = 'list' 10 | } 11 | 12 | export interface OrderbookPanelProps { 13 | kind: OrderbookViewKind; 14 | } 15 | 16 | export interface SubViewsProps { 17 | DepthChartWithLoadingTxRx : React.ComponentType; 18 | OrderbookViewTxRx: React.ComponentType; 19 | } 20 | 21 | export class OrderbookPanel extends React.Component { 22 | public render() { 23 | if (this.props.kind === OrderbookViewKind.depthChart) { 24 | return (); 25 | } 26 | return (); 27 | } 28 | } 29 | 30 | export function createOrderbookPanel$(): [ 31 | (kind: OrderbookViewKind) => void, 32 | Observable 33 | ] { 34 | const kind$ = new BehaviorSubject(OrderbookViewKind.list); 35 | return [ 36 | kind$.next.bind(kind$), 37 | kind$.pipe( 38 | map(kind => ({ 39 | kind, 40 | })) 41 | ) 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /src/icons/providers/status.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /codechecks.ts: -------------------------------------------------------------------------------- 1 | import { codechecks } from '@codechecks/client'; 2 | import { join } from 'path'; 3 | // tslint:disable-next-line 4 | const exec = require('await-exec') as (cmd: string, opt: any) => Promise; 5 | 6 | export async function main() { 7 | await visReg(); 8 | } 9 | 10 | async function visReg() { 11 | const execOptions = { timeout: 300000, cwd: process.cwd(), log: true }; 12 | await exec('yarn storybook:screenshots', execOptions); 13 | await codechecks.saveDirectory('storybook-vis-reg', join(__dirname, '__screenshots__')); 14 | 15 | if (codechecks.isPr()) { 16 | await codechecks.getDirectory('storybook-vis-reg', join(__dirname, '.reg/expected')); 17 | await exec('./node_modules/.bin/reg-suit compare', execOptions); 18 | 19 | await codechecks.saveDirectory('storybook-vis-reg-report', join(__dirname, '.reg')); 20 | 21 | const reportData = require('./.reg/out.json'); 22 | await codechecks.success({ 23 | name: 'Visual regression for Storybook', 24 | shortDescription: `Changed: ${reportData.failedItems.length}, New: ${reportData.newItems.length}, Deleted: ${reportData.deletedItems.length}`, 25 | detailsUrl: codechecks.getArtifactLink('/storybook-vis-reg-report/index.html'), 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/instant/details/TradeData.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | import { WarningTooltip, WarningTooltipType } from '../../utils/tooltip/Tooltip'; 4 | import * as styles from './TradeData.scss'; 5 | 6 | interface EntryProps { 7 | label: string | React.ReactNode; 8 | style?: React.CSSProperties; 9 | value?: string | React.ReactNode; 10 | info?: string; 11 | theme?: 'reversed'; 12 | tooltip?: WarningTooltipType; 13 | } 14 | 15 | const skin = (theme: string) => { 16 | switch (theme) { 17 | case 'reversed': 18 | return styles.reversed; 19 | default: 20 | return styles.entry; 21 | } 22 | }; 23 | 24 | export class TradeData extends React.Component { 25 | public render() { 26 | const { label, value, tooltip, theme, ...rest } = this.props; 27 | return ( 28 |
29 | 30 | {label} 31 |   32 | { 33 | tooltip && 34 | } 35 | 37 | {value} 38 | 39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/exchange/ExchangeView.scss: -------------------------------------------------------------------------------- 1 | @import "../utils/Mixins"; 2 | 3 | .tradingPairPanel { 4 | flex-grow: 1; 5 | flex-direction: row; 6 | border: none; 7 | z-index: 1; 8 | &.pairPickerOpen { 9 | border-bottom-left-radius: 0; 10 | } 11 | 12 | @include media-breakpoint-down(md-lg){ 13 | flex-direction: column; 14 | border: 1px solid $grey-darken-darkest; 15 | } 16 | } 17 | 18 | $historyPanelsHeight: 342px; 19 | $offerMakePanelHeight: 474px; 20 | $twoColumnsPanelWidth: 454px; 21 | 22 | .priceChartPanel, 23 | .allTradesPanel { 24 | width: $twoColumnsPanelWidth; 25 | height: $historyPanelsHeight; 26 | 27 | @include media-breakpoint-down(lg) { 28 | width: 100%; 29 | } 30 | } 31 | 32 | 33 | .offerMakePanel { 34 | height: $offerMakePanelHeight; 35 | width: $twoColumnsPanelWidth; 36 | margin-right: 24px; 37 | flex-grow: 1; 38 | @include media-breakpoint-down(lg) { 39 | margin-right: 0; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | } 44 | 45 | .orderbookPanel { 46 | height: $offerMakePanelHeight; 47 | flex-basis: $twoColumnsPanelWidth; 48 | flex-grow: 1; 49 | 50 | @include media-breakpoint-down(lg) { 51 | width: 100%; 52 | height: $historyPanelsHeight; 53 | } 54 | } 55 | 56 | .myOrdersPanel { 57 | min-height: 284px; 58 | width: 100%; 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/operators.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export const withSingleFrom = (other: Observable) => (source: Observable) => 4 | new Observable<[T, U?]>(observer => { 5 | let latest: T; 6 | const subscription = source.subscribe({ 7 | next(t) { latest = t; observer.next([t, undefined]); }, 8 | error(err) { observer.error(err); }, 9 | complete() { observer.complete(); } 10 | }); 11 | subscription.add(other.subscribe({ 12 | next(u) { observer.next([latest, u]); }, 13 | error(err) { observer.error(err); }, 14 | complete() { observer.complete(); } 15 | })); 16 | return subscription; 17 | }); 18 | 19 | // emits the first item satisfying the predicate and all items not satisfying the predicate 20 | export const firstOfOrTrue = (predicate: (item: T) => boolean) => (source: Observable) => 21 | new Observable(observer => { 22 | let satisfied: boolean = false; 23 | return source.subscribe({ 24 | next(t) { 25 | if (predicate(t)) { 26 | if (!satisfied) observer.next(t); 27 | satisfied = true; 28 | } else { 29 | observer.next(t); 30 | } 31 | }, 32 | error(err) { observer.error(err); }, 33 | complete() { observer.complete(); } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/instant/views/TradeSettingsView.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Variables"; 2 | 3 | .settings { 4 | width: 100%; 5 | padding: 1rem; 6 | border: 1px solid $button-border-disabled; 7 | border-radius: .25rem; 8 | } 9 | 10 | .parameter { 11 | display: flex; 12 | flex-direction: column; 13 | width: 50%; 14 | 15 | .name { 16 | display: inline-block; 17 | font-size: 12px; 18 | color: rgba(255, 255, 255, 0.9); 19 | letter-spacing: 1px; 20 | margin-bottom: .75rem; 21 | } 22 | 23 | .value { 24 | text-align: left; 25 | padding: 0 1rem; 26 | } 27 | } 28 | 29 | .warning { 30 | display: flex; 31 | align-items: center; 32 | width: 100%; 33 | padding: .875rem; 34 | margin-top: 1rem; 35 | border: 1px solid $button-border-disabled; 36 | border-radius: .25rem; 37 | 38 | .icon { 39 | :global(.warning-icon) { 40 | fill: rgba(255, 255, 255, 0.9); 41 | } 42 | 43 | height: 24px; 44 | width: 24px; 45 | margin-right: 12px; 46 | } 47 | 48 | .text { 49 | font-weight: 500; 50 | font-size: 12px; 51 | width: fit-content; 52 | color: $grey-lighter; 53 | text-align: left; 54 | letter-spacing: .7px; 55 | line-height: 16px; 56 | margin: 0; 57 | } 58 | 59 | .highlight { 60 | font-weight: 600; 61 | color: white; 62 | } 63 | } -------------------------------------------------------------------------------- /src/utils/forms/InputGroup.scss: -------------------------------------------------------------------------------- 1 | @import "../Mixins"; 2 | @import "../Variables"; 3 | 4 | .inputGroup { 5 | display: flex; 6 | background-color: $grey-darkest; 7 | 8 | & > * { 9 | border-top: 1px solid $grey-light; 10 | border-bottom: 1px solid $grey-light; 11 | } 12 | 13 | & > *:first-child { 14 | border-top-left-radius: 2px; 15 | border-bottom-left-radius: 2px; 16 | border-left: 1px solid $grey-light; 17 | } 18 | 19 | & > *:last-child { 20 | border-top-right-radius: 2px; 21 | border-bottom-right-radius: 2px; 22 | border-right: 1px solid $grey-light; 23 | } 24 | } 25 | 26 | .addon { 27 | display: inline-flex; 28 | align-items: center; 29 | justify-content: center; 30 | padding: 5px 20px; 31 | font-size: 14px; 32 | } 33 | 34 | // ----------- sizes 35 | @include formInputSizes(); 36 | .sm, .md { 37 | .addon, input { 38 | font-size: 12px; 39 | } 40 | } 41 | 42 | .inputGroupAddonBorderRight { 43 | border-right: 1px solid; 44 | border-color: inherit; 45 | } 46 | 47 | .inputGroupAddonBorderLeft { 48 | border-left: 1px solid; 49 | border-color: inherit; 50 | } 51 | 52 | .grey { 53 | border-color: $grey-light; 54 | } 55 | 56 | .red { 57 | border-color: $red; 58 | } 59 | 60 | .disabled { 61 | border-color: $disabledBorderColor; 62 | color: $disabledColor; 63 | } -------------------------------------------------------------------------------- /src/icons/coins/mkr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/bigNumberInput/BigNumberInput.tsx: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import classnames from 'classnames'; 3 | import * as React from 'react'; 4 | import { default as MaskedInput } from 'react-text-mask'; 5 | import * as styles from './BigNumberInput.scss'; 6 | 7 | export class BigNumberInput extends React.Component { 8 | private lastValue?: string; 9 | 10 | public changed = (e: React.ChangeEvent): void => { 11 | this.lastValue = e.target.value; 12 | if (e.target.value !== '0._') { 13 | this.props.onChange(e); 14 | } 15 | } 16 | 17 | public render() { 18 | const currentValue: string | undefined = this.props.value; 19 | let value: string | undefined; 20 | if ( 21 | this.lastValue && 22 | currentValue && 23 | new BigNumber(this.lastValue.replace(/\,/g, '')).eq( 24 | new BigNumber(currentValue.replace(/\,/g, '')) 25 | ) 26 | ) { 27 | value = this.lastValue; 28 | } else { 29 | value = currentValue; 30 | } 31 | 32 | return ( 33 | // @ts-ignore 34 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/icons/providers/parity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/utils/forms/Select.scss: -------------------------------------------------------------------------------- 1 | @import "../Mixins"; 2 | @import "../Variables"; 3 | 4 | .select { 5 | $padding-vertical: 5px; 6 | $padding-horizontal: 20px; 7 | $padding-right-extra: 6px; 8 | 9 | @include fontMontserratMedium; 10 | border: none; 11 | background-color: transparent; 12 | color: $font-color-base; 13 | font-size: 14px; 14 | letter-spacing: 0.5px; 15 | line-height: 1; 16 | padding: $padding-vertical $padding-horizontal+$padding-right-extra $padding-vertical $padding-horizontal; 17 | text-transform: uppercase; 18 | width: 100%; 19 | 20 | &:not(:disabled) { 21 | cursor: pointer; 22 | } 23 | 24 | &:disabled { 25 | color: $grey-lightest; 26 | } 27 | 28 | -moz-appearance: none; 29 | -webkit-appearance: none; 30 | 31 | option { 32 | background-color: $grey-dark; 33 | } 34 | } 35 | 36 | .wrapperBordered { 37 | border: 1px solid $grey-light; 38 | border-radius: 2px; 39 | } 40 | 41 | .wrapper { 42 | 43 | position: relative; 44 | display: flex; 45 | justify-content: stretch; 46 | align-items: stretch; 47 | 48 | background: url("./icon_arrow_down.svg") no-repeat center right; 49 | } 50 | 51 | .disabled { 52 | // .wrapper 53 | background-image: url("./icon_arrow_down_grey.svg"); 54 | // .wrapperBordered 55 | border-color: $grey-dark; 56 | 57 | } 58 | 59 | @include formInputSizes(); 60 | -------------------------------------------------------------------------------- /src/utils/gasCost/GasCost.tsx: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import * as React from 'react'; 3 | 4 | import { GasEstimationStatus, HasGasEstimation } from '../form'; 5 | import { Money } from '../formatters/Formatters'; 6 | import { Muted } from '../text/Text'; 7 | 8 | export const GasCost = (props: HasGasEstimation) => { 9 | 10 | const { gasEstimationStatus, gasEstimationEth, gasEstimationUsd } = props; 11 | 12 | switch (gasEstimationStatus) { 13 | case GasEstimationStatus.calculating: 14 | return ...; 15 | case GasEstimationStatus.error: 16 | return error; 17 | case GasEstimationStatus.unknown: 18 | return -; 19 | case GasEstimationStatus.unset: 20 | case undefined: 21 | case GasEstimationStatus.calculated: 22 | const usd = gasEstimationUsd || new BigNumber(0); 23 | const eth = gasEstimationEth || new BigNumber(0); 24 | // tslint:disable-next-line:max-line-length 25 | return ( {/* This space fixes this issue https://stackoverflow.com/questions/48704520/bootstrap-why-does-double-click-select-in-one-span-also-select-text-in-another*/ /* ts-ignore-line */} 26 | ~  27 | 28 | ); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /cypress/integration/instant/AccountSettings.spec.ts: -------------------------------------------------------------------------------- 1 | import { clear, create, hasStatus, ProxyStatus, settings } from '../../pages/Proxy'; 2 | import { Tab } from '../../pages/Tab'; 3 | import { WalletConnection } from '../../pages/WalletConnection'; 4 | import { cypressVisitWithWeb3, tid } from '../../utils'; 5 | 6 | describe('Account Settings', () => { 7 | context('without connected wallet', () => { 8 | beforeEach(() => { 9 | cypressVisitWithWeb3(); 10 | Tab.instant(); 11 | }); 12 | 13 | it('should disable account settings', () => { 14 | cy.get(tid('account-settings')).should('be.disabled'); 15 | }); 16 | }); 17 | 18 | context('with connected wallet', () => { 19 | beforeEach(() => { 20 | cypressVisitWithWeb3(); 21 | WalletConnection.connect(); 22 | WalletConnection.isConnected(); 23 | Tab.instant(); 24 | }); 25 | 26 | it('should create new proxy', () => { 27 | settings().click(); 28 | hasStatus(ProxyStatus.MISSING); 29 | create(); 30 | hasStatus(ProxyStatus.ENABLED); 31 | }); 32 | 33 | it('should have missing proxy if user deletes proxy manually', () => { 34 | settings().click(); 35 | hasStatus(ProxyStatus.MISSING); 36 | create(); 37 | hasStatus(ProxyStatus.ENABLED); 38 | clear(); 39 | hasStatus(ProxyStatus.MISSING); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/exchange/myTrades/MyTradesView.scss: -------------------------------------------------------------------------------- 1 | @import "../../utils/Mixins"; 2 | 3 | .container { 4 | min-height: 230px; 5 | } 6 | 7 | .orderTypeBtn { 8 | width: 86px; 9 | 10 | @include media-breakpoint-down(sm){ 11 | width: 72px; 12 | font-size: 11px; 13 | } 14 | } 15 | 16 | @include tableTextAlign(right); 17 | 18 | .myTradesTable { 19 | max-height: 400px; 20 | 21 | td, th { 22 | &:first-child { 23 | padding-left: 16px; 24 | @include media-breakpoint-up(lg) { 25 | padding-left: 24px; 26 | width: 100px; 27 | } 28 | } 29 | } 30 | 31 | 32 | } 33 | 34 | .myOpenTradesTable { 35 | @include tableColumnsWidth(100px 165px 165px 165px 165px 165px); 36 | @include tableColumnsWidth(50px 85px 85px 85px 60px 75px, lg); 37 | 38 | // status with icon 39 | td:last-child { 40 | white-space: nowrap; 41 | } 42 | } 43 | 44 | .myCloseTradesTable { 45 | @include tableColumnsWidth(100px 165px 165px 165px 165px); 46 | @include tableColumnsWidth(60px 100px 100px 100px 90px, lg); 47 | } 48 | 49 | .status { 50 | display: inline-flex; 51 | align-items: center; 52 | justify-content: flex-end; 53 | width: 100%; 54 | } 55 | 56 | .statusText { 57 | display: inline-block; 58 | margin-right: 1em; 59 | text-transform: uppercase; 60 | 61 | } 62 | 63 | .statusProgress { 64 | display: inline-block; 65 | position: relative; 66 | } 67 | -------------------------------------------------------------------------------- /cypress/pages/Order.ts: -------------------------------------------------------------------------------- 1 | import { tid } from '../utils/index'; 2 | 3 | export class Order { 4 | 5 | public sell = () => { 6 | cy.get(tid('new-sell-order')).click(); 7 | return this; 8 | } 9 | 10 | public buy = () => { 11 | cy.get(tid('new-buy-order')).click(); 12 | return this; 13 | } 14 | 15 | public limit = () => { 16 | cy.get(tid('select-order-type')).click(); 17 | cy.get(tid('limitOrder'), { timeout: 5000 }).click(); 18 | cy.get(tid('submit')).click(); 19 | return this; 20 | } 21 | 22 | public fillOrKill = () => { 23 | cy.get(tid('select-order-type')).click(); 24 | cy.get(tid('fillOrKill'), { timeout: 5000 }).click(); 25 | cy.get(tid('submit')).click(); 26 | return this; 27 | } 28 | 29 | public direct = () => { 30 | cy.get(tid('select-order-type', tid('direct'))).click(); 31 | return this; 32 | } 33 | 34 | public amount = (amount: string) => { 35 | cy.get(tid('type-amount')).type(amount); 36 | return this; 37 | } 38 | 39 | public atPrice = (price: string) => { 40 | cy.get(tid('type-price')).type(`{selectall}${price}`); 41 | return this; 42 | } 43 | 44 | public total = (total: string) => { 45 | cy.get(tid('type-total')).contains('span', total, { timeout: 5000 }); 46 | return this; 47 | } 48 | 49 | public place = () => { 50 | cy.get(tid('place-order')).click(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | // const path = require("path"); 2 | // const TSDocgenPlugin = require("react-docgen-typescript-webpack-plugin"); 3 | 4 | module.exports = (baseConfig, env, config) => { 5 | config.module.rules.forEach(rule => { 6 | const test = rule.test.toString(); 7 | if (test.match(/\|svg/)) 8 | rule.test = new RegExp(test.substr(1, test.length - 2).replace(/\|svg/, '')); 9 | }); 10 | config.module.rules.push({ 11 | test: /\.svg$/, 12 | use: [ 13 | { 14 | loader: require.resolve("url-loader"), 15 | options: { 16 | limit: 20000, 17 | } 18 | }, 19 | ], 20 | }); 21 | 22 | config.module.rules.push({ 23 | test: /\.(ts|tsx)$/, 24 | loader: require.resolve("ts-loader"), 25 | }); 26 | 27 | config.module.rules.push({ 28 | test: /\.scss$/, 29 | use: [ 30 | { 31 | loader: require.resolve("style-loader"), // creates style nodes from JS strings 32 | }, 33 | { 34 | loader: require.resolve("typings-for-css-modules-loader"), 35 | options: { 36 | modules: true, 37 | namedExport: true, 38 | camelCase: true, 39 | }, 40 | }, 41 | { 42 | loader: require.resolve("sass-loader"), // compiles Sass to CSS 43 | }, 44 | ], 45 | }); 46 | 47 | config.resolve.extensions.push(".ts", ".tsx"); 48 | 49 | return config; 50 | }; 51 | -------------------------------------------------------------------------------- /cypress/pages/TradingPairDropdown.ts: -------------------------------------------------------------------------------- 1 | // This should be taken from src/exchange/tradingPair/tradingPair.ts 2 | // but for now there are issues with importing project ( src ) files. 3 | import { tid } from '../utils'; 4 | 5 | export interface TradingPair { 6 | base: string; 7 | quote: string; 8 | } 9 | 10 | export class TradingPairInfo { 11 | public static lastPrice = () => 12 | cy.get(tid('trading-pair-info', tid('last-price', tid('value')))) 13 | public static dailyVolume = () => 14 | cy.get(tid('trading-pair-info', tid('24h-volume', tid('value')))) 15 | } 16 | 17 | export class TradingPairDropdown { 18 | public static expand() { 19 | cy.get(tid('select-pair')).click(); 20 | } 21 | 22 | public static hasMarkets(tradingPairs: TradingPair[]) { 23 | tradingPairs.forEach((pair) => { 24 | cy.get(tid(`${pair.base}-${pair.quote}`, tid('base'))).contains(pair.base); 25 | cy.get(tid(`${pair.base}-${pair.quote}`, tid('quote'))).contains(pair.quote); 26 | }); 27 | } 28 | 29 | public static expectActivePAir(tradingPair: TradingPair) { 30 | cy.get(tid('active-pair', tid('base'))).contains(tradingPair.base); 31 | cy.get(tid('active-pair', tid('quote'))).contains(tradingPair.quote); 32 | 33 | } 34 | 35 | public static select(tradingPair: TradingPair) { 36 | TradingPairDropdown.expand(); 37 | cy.get(tid(`${tradingPair.base}-${tradingPair.quote}`)).click(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/header/Header.scss.d.ts: -------------------------------------------------------------------------------- 1 | export const button: string; 2 | export const disabled: string; 3 | export const buttonGroup: string; 4 | export const grey: string; 5 | export const whiteOutlined: string; 6 | export const green: string; 7 | export const red: string; 8 | export const white: string; 9 | export const greyWhite: string; 10 | export const xs: string; 11 | export const sm: string; 12 | export const md: string; 13 | export const lg: string; 14 | export const unsized: string; 15 | export const full: string; 16 | export const block: string; 17 | export const actionButton: string; 18 | export const close: string; 19 | export const darkRed: string; 20 | export const header: string; 21 | export const logo: string; 22 | export const section: string; 23 | export const sectionNavigation: string; 24 | export const sectionStatus: string; 25 | export const nav: string; 26 | export const list: string; 27 | export const item: string; 28 | export const navLink: string; 29 | export const navElement: string; 30 | export const activeNavLink: string; 31 | export const account: string; 32 | export const arrowDown: string; 33 | export const accountLoader: string; 34 | export const login: string; 35 | export const connectWalletButton: string; 36 | export const networkIndicator: string; 37 | export const kovan: string; 38 | export const main: string; 39 | export const dark: string; 40 | export const light: string; 41 | export const walletConnection: string; 42 | -------------------------------------------------------------------------------- /src/landingPage/Banner.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // @ts-ignore 3 | // @ts-ignore 4 | // @ts-ignore 5 | // tslint:disable:import-name 6 | 7 | import warningSvg from '../icons/warning.svg'; 8 | import { Button } from '../utils/forms/Buttons'; 9 | import { SvgImage } from '../utils/icons/utils'; 10 | import * as styles from './Banner.scss'; 11 | 12 | interface BannerProps { 13 | buttonLabel: string | React.ReactNode; 14 | content: string | React.ReactNode; 15 | continue: () => any; 16 | } 17 | 18 | export class Banner extends React.Component { 19 | public render() { 20 | 21 | const { content, continue: onContinue, buttonLabel } = this.props; 22 | 23 | return ( 24 |
25 |
26 |
27 | 28 |
29 | {content} 30 |
31 |
32 |
33 | 39 |
40 |
41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/tooltip/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | import * as ReactTooltip from 'react-tooltip'; 4 | import warningSvg from '../../icons/warning.svg'; 5 | import { SvgImage } from '../icons/utils'; 6 | import * as styles from './Tooltip.scss'; 7 | 8 | export interface TooltipType { 9 | id: string; 10 | text: string; 11 | } 12 | 13 | export interface WarningTooltipType extends TooltipType { 14 | iconColor?: 'white' | 'grey' | 'soft-cyan'; 15 | } 16 | 17 | export class WarningTooltip extends React.Component { 18 | public render() { 19 | const { id, text, iconColor } = this.props; 20 | return ( 21 | 22 | 27 | 28 | ); 29 | } 30 | } 31 | 32 | export class Tooltip extends React.Component { 33 | public render() { 34 | const { id, text, children } = this.props; 35 | return (<> 36 | {children} 37 | 38 |

{text}

39 |
40 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/icons/Icons.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | import { Button, ButtonProps } from '../forms/Buttons'; 4 | import * as styles from './Icons.scss'; 5 | import { SvgImage } from './utils'; 6 | 7 | export const InfoIcon = (props: React.HTMLAttributes) => { 8 | const { className, ...otherProps } = props; 9 | return ( 10 |
i
14 | ); 15 | }; 16 | 17 | export const ButtonIcon = (props: ButtonProps & { image: any }) => { 18 | const { className, image, ...otherProps } = props; 19 | return ( 20 | 26 | ); 27 | }; 28 | 29 | export type ProgressIconProps = React.HTMLAttributes & { 30 | light?: boolean, 31 | size?: 'sm' | 'lg', 32 | }; 33 | 34 | export const ProgressIcon = (props: ProgressIconProps) => { 35 | const { className, light, size, ...otherProps } = props; 36 | return ( 37 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/blockchain/calls/txMeta.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { OfferType } from '../../exchange/orderbook/orderbook'; 3 | 4 | export enum TxMetaKind { 5 | cancel = 'cancel', 6 | offerMake = 'offerMake', 7 | approveWallet = 'approveWallet', 8 | disapproveWallet = 'disapproveWallet', 9 | wrap = 'wrap', 10 | unwrap = 'unwrap', 11 | tradePayWithETHWithProxy = 'tradePayWithETHWithProxy', 12 | tradePayWithETHNoProxy = 'tradePayWithETHWithProxy', 13 | tradePayWithERC20 = 'tradePayWithERC20', 14 | setupProxy = 'setupProxy', 15 | approveProxy = 'approveProxy', 16 | disapproveProxy = 'disapproveProxy' 17 | } 18 | 19 | export type TxMeta = { 20 | description: string 21 | } & ( 22 | { 23 | kind: TxMetaKind.cancel, 24 | offerId: BigNumber, 25 | } | { 26 | kind: TxMetaKind.offerMake, 27 | act: OfferType, 28 | baseAmount: BigNumber, 29 | baseToken: string, 30 | quoteAmount: BigNumber, 31 | quoteToken: string, 32 | price: BigNumber, 33 | } | { 34 | kind: TxMetaKind.approveWallet, 35 | token: string 36 | } | { 37 | kind: TxMetaKind.disapproveWallet, 38 | token: string 39 | } | { 40 | kind: TxMetaKind.wrap, 41 | amount: BigNumber, 42 | } | { 43 | kind: TxMetaKind.unwrap, 44 | amount: BigNumber, 45 | } | { 46 | kind: TxMetaKind.tradePayWithETHWithProxy | TxMetaKind.tradePayWithETHNoProxy, 47 | buyAmount: BigNumber, 48 | sellAmount: BigNumber, 49 | buyToken: string, 50 | sellToken: string, 51 | }); 52 | -------------------------------------------------------------------------------- /src/instant/InstantFormWrapper.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | import { Button } from '../utils/forms/Buttons'; 4 | import * as panelStyling from '../utils/panel/Panel.scss'; 5 | import * as styles from './Instant.scss'; 6 | 7 | interface InstantFormProps { 8 | heading: string | React.ReactNode; 9 | btnLabel?: string; 10 | btnAction?: () => void; 11 | btnDisabled?: boolean; 12 | btnDataTestId?: string; 13 | } 14 | 15 | export class InstantFormWrapper extends React.Component { 16 | public render() { 17 | const { heading, btnLabel, btnAction, btnDisabled, btnDataTestId, children } = this.props; 18 | 19 | return ( 20 |
21 |
22 |

{heading}

23 |
24 | { 25 | children 26 | } 27 | { 28 | btnLabel && ( 29 |
30 | 40 |
41 | ) 42 | } 43 |
44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /scripts/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | localnode: 4 | image: oasisdexorg/localnode:1efd6e1204f725278e552d263c14cb4a7f0392bb 5 | command: master 6 | ports: 7 | - "8545:8545" 8 | 9 | postgres: 10 | image: postgres:10.6 11 | container_name: postgres-vulcan2x 12 | environment: 13 | - POSTGRES_USER=user 14 | - POSTGRES_PASSWORD=password 15 | - POSTGRES_HOST=localhost 16 | - POSTGRES_PORT=5432 17 | - POSTGRES_DB=database 18 | ports: 19 | - "5432:5432" 20 | 21 | oasis-cache-etl: 22 | image: oasisdexorg/oasis-cache:726c3bbca40aa2647af0b0a6c8f0202badaa4639 23 | environment: 24 | VL_DB_DATABASE: database 25 | VL_DB_USER: user 26 | VL_DB_PASSWORD: password 27 | VL_DB_HOST: postgres 28 | VL_DB_PORT: 5432 29 | VL_CHAIN_NAME: localnet 30 | VL_CHAIN_HOST: http://localnode:8545 31 | entrypoint: sh -c 32 | command: 33 | - sleep 10 && yarn migrate && yarn start-etl 34 | 35 | oasis-cache-api: 36 | image: oasisdexorg/oasis-cache:726c3bbca40aa2647af0b0a6c8f0202badaa4639 37 | environment: 38 | VL_DB_DATABASE: database 39 | VL_DB_USER: user 40 | VL_DB_PASSWORD: password 41 | VL_DB_HOST: postgres 42 | VL_DB_PORT: 5432 43 | VL_CHAIN_NAME: localnet 44 | VL_CHAIN_HOST: http://localnode:8545 45 | ports: 46 | - "3001:3001" 47 | entrypoint: sh -c 48 | command: 49 | - sleep 15 && yarn start-api 50 | -------------------------------------------------------------------------------- /src/utils/icons/Icons.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | 3 | .infoIcon { 4 | $iconSize: 22px; 5 | 6 | color: $grey-lightest; 7 | 8 | border: 1px solid $grey-lightest; 9 | border-radius: 100%; 10 | 11 | width: $iconSize; 12 | height: $iconSize; 13 | 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | flex-shrink: 0; 18 | 19 | font-size: 12px; 20 | font-weight: bold; 21 | } 22 | 23 | // progress icon with spin animation 24 | @keyframes spin { 25 | 100% { 26 | -webkit-transform: rotate(360deg); 27 | transform:rotate(360deg); 28 | } 29 | } 30 | 31 | .progressIcon { 32 | $iconSize: 16px; 33 | 34 | display: inline-block; 35 | 36 | border: 2px solid $grey-light; 37 | border-right-color: white; 38 | border-radius: 100%; 39 | 40 | width: $iconSize; 41 | height: $iconSize; 42 | 43 | animation: spin 3s linear infinite; 44 | } 45 | 46 | .progressIconLight { 47 | border-color: #eee; 48 | border-right-color: #448AFF; 49 | } 50 | 51 | .progressIconSm { 52 | $size: 12px; 53 | width: $size; 54 | height: $size; 55 | } 56 | 57 | .progressIconLg { 58 | $size: 26px; 59 | width: $size; 60 | height: $size; 61 | } 62 | 63 | .socialIcon { 64 | path { 65 | fill: #B3B3B9; 66 | } 67 | } 68 | 69 | .socialIcon:hover { 70 | path { 71 | fill: #FFF; 72 | } 73 | } 74 | 75 | .btnIcon { 76 | height: 100%; 77 | width: 100%; 78 | display: inline-flex; 79 | align-items: center; 80 | justify-content: center; 81 | } -------------------------------------------------------------------------------- /src/blockchain/abi/ds-proxy-factory.abi.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "constant": true, 3 | "inputs": [{ 4 | "name": "", 5 | "type": "address" 6 | }], 7 | "name": "isProxy", 8 | "outputs": [{ 9 | "name": "", 10 | "type": "bool" 11 | }], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, { 16 | "constant": true, 17 | "inputs": [], 18 | "name": "cache", 19 | "outputs": [{ 20 | "name": "", 21 | "type": "address" 22 | }], 23 | "payable": false, 24 | "stateMutability": "view", 25 | "type": "function" 26 | }, { 27 | "constant": false, 28 | "inputs": [], 29 | "name": "build", 30 | "outputs": [{ 31 | "name": "proxy", 32 | "type": "address" 33 | }], 34 | "payable": false, 35 | "stateMutability": "nonpayable", 36 | "type": "function" 37 | }, { 38 | "constant": false, 39 | "inputs": [{ 40 | "name": "owner", 41 | "type": "address" 42 | }], 43 | "name": "build", 44 | "outputs": [{ 45 | "name": "proxy", 46 | "type": "address" 47 | }], 48 | "payable": false, 49 | "stateMutability": "nonpayable", 50 | "type": "function" 51 | }, { 52 | "anonymous": false, 53 | "inputs": [{ 54 | "indexed": true, 55 | "name": "sender", 56 | "type": "address" 57 | }, { 58 | "indexed": false, 59 | "name": "proxy", 60 | "type": "address" 61 | }, { 62 | "indexed": false, 63 | "name": "cache", 64 | "type": "address" 65 | }], 66 | "name": "Created", 67 | "type": "event" 68 | }] 69 | -------------------------------------------------------------------------------- /src/utils/markets.test.ts: -------------------------------------------------------------------------------- 1 | import { marketsOf } from './markets'; 2 | 3 | test('should return all possible markets for a given token', () => { 4 | const pairs = [ 5 | { base: 'WETH', quote: 'MKR' }, 6 | { base: 'MKR', quote: 'DAI' }, 7 | { base: 'MKR', quote: 'USDC' }, 8 | { base: 'WETH', quote: 'USDC' }, 9 | { base: 'WETH', quote: 'WBTC' }, 10 | ]; 11 | 12 | const availableMarkets = new Set(['WETH', 'DAI', 'USDC']); 13 | 14 | expect(marketsOf('MKR', pairs)).toEqual(availableMarkets); 15 | }); 16 | 17 | test('should return empty set if no markets are found for the given token', () => { 18 | const pairs = [ 19 | { base: 'WETH', quote: 'MKR' }, 20 | { base: 'MKR', quote: 'DAI' }, 21 | { base: 'MKR', quote: 'USDC' }, 22 | { base: 'WETH', quote: 'USDC' }, 23 | { base: 'WETH', quote: 'WBTC' }, 24 | ]; 25 | 26 | const availableMarkets = new Set([]); 27 | 28 | expect(marketsOf('DGD', pairs)).toEqual(availableMarkets); 29 | }); 30 | 31 | test('should return empty set if no markets are available at all', () => { 32 | const availableMarkets = new Set([]); 33 | 34 | expect(marketsOf('DGD', [])).toEqual(availableMarkets); 35 | }); 36 | 37 | test('should include ETH token as WETH', () => { 38 | const pairs = [ 39 | { base: 'ETH', quote: 'MKR' }, 40 | { base: 'MKR', quote: 'DAI' }, 41 | ]; 42 | 43 | const availableMarkets = new Set(['WETH', 'DAI']); 44 | 45 | expect(marketsOf('MKR', pairs)).toEqual(availableMarkets); 46 | }); 47 | -------------------------------------------------------------------------------- /src/utils/combineAndMerge.ts: -------------------------------------------------------------------------------- 1 | import { difference } from 'lodash'; 2 | import { combineLatest, Observable, of } from 'rxjs/index'; 3 | import { mergeMap, scan } from 'rxjs/operators'; 4 | 5 | type O = Observable; 6 | 7 | export function combineAndMerge(o1: O, o2: O): O; 8 | export function combineAndMerge( 9 | o1: O, o2: O, o3: O 10 | ): O; 11 | export function combineAndMerge( 12 | o1: O, o2: O, o3: O, o4: O 13 | ): O; 14 | export function combineAndMerge( 15 | o1: O, o2: O, o3: O, o4: O, o5: O 16 | ): O; 17 | export function combineAndMerge( 18 | o1: O, o2: O, o3: O, o4: O, o5: O, o6: O 19 | ): O; 20 | export function combineAndMerge( 21 | o1: O, o2: O, o3: O, o4: O, o5: O, o6: O, o7: O 22 | ): O; 23 | export function combineAndMerge( 24 | o1: O, o2: O, o3: O, o4: O, o5: O, o6: O, o7: O, o8: O 25 | ): O; 26 | export function combineAndMerge(...observables: Array>): O { 27 | return combineLatest(...observables).pipe( 28 | scan((previous: any[], current: any) => difference(current, previous), []), 29 | mergeMap(values => of(...values)) 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/loadingIndicator/ServerUnreachable.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import errorSvg from '../../icons/error.svg'; 5 | import { SvgImage } from '../icons/utils'; 6 | import { Muted } from '../text/Text'; 7 | import * as styles from './ServerUnreachable.scss'; 8 | 9 | export const ServerUnreachable = ({ className, ...props }: { 10 | className?: string 11 | }) => { 12 | return ( 13 |
17 |
18 | 19 | Server unreachable 20 |
21 | 22 | Please try again later or 27 | Contact us 28 | 29 | 30 |
31 | ); 32 | }; 33 | 34 | export const ServerUnreachableInline = ({ className, fallback, ...props }: { 35 | className?: string 36 | fallback?: string | React.ReactChild, 37 | }) => { 38 | return ( 39 |
44 | 45 | {fallback} 46 |
47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /src/icons/coins/mkr-inverse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | @import "utils/Mixins"; 2 | @import "utils/Variables"; 3 | 4 | * { 5 | -webkit-box-sizing: border-box; 6 | -moz-box-sizing: border-box; 7 | box-sizing: border-box; 8 | } 9 | 10 | html { 11 | margin-left: calc(100vw - 100%); 12 | } 13 | 14 | body { 15 | @include fontMontserratMedium; 16 | margin: 0; 17 | padding: 0; 18 | background-color: $body-bg; 19 | color: $font-color-base; 20 | font-size: $font-size-base; 21 | } 22 | 23 | .container { 24 | width: 932px; 25 | margin: 0 auto; 26 | position: relative; 27 | 28 | @include media-breakpoint-down(lg) { 29 | width: auto; 30 | margin-left: 1em; 31 | margin-right: 1em; 32 | } 33 | } 34 | 35 | 36 | // ----------- Scrollbar styling for webkit ----------- 37 | ::-webkit-scrollbar { 38 | width: 8px; 39 | height: 8px; 40 | } 41 | 42 | ::-webkit-scrollbar-track { 43 | background-color: $grey-darkest; 44 | padding: 20px; 45 | } 46 | 47 | ::-webkit-scrollbar-thumb { 48 | background-color: #eeeeee; 49 | border-radius: 4px; 50 | } 51 | 52 | ::-webkit-scrollbar-corner { 53 | background-color: rgba(white, 0.25); 54 | border-radius: 100%; 55 | } 56 | 57 | :global(.ReactModal__Content) { 58 | opacity: 0; 59 | } 60 | 61 | :global(.ReactModal__Content--after-open) { 62 | opacity: 1; 63 | transition: opacity 250ms ease-in-out; 64 | } 65 | 66 | :global(.ReactModal__Content--before-close) { 67 | opacity: 0; 68 | transition: opacity 250ms ease-out; 69 | } 70 | 71 | :global(a) { 72 | color: inherit; 73 | text-decoration: none; 74 | } -------------------------------------------------------------------------------- /src/utils/Scrollbar/Scrollbar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { default as Scrollbars, positionValues } from 'react-custom-scrollbars'; 3 | import * as styles from './Scrollbar.scss'; 4 | 5 | export type ScrollState = positionValues; 6 | 7 | interface ScrollbarProps { 8 | autoHeight?: boolean; 9 | onScroll?: () => void; 10 | } 11 | 12 | export class Scrollbar extends React.Component { 13 | 14 | private scroll = React.createRef(); 15 | 16 | public center(elementOffset: number, elementHeight: number): void { 17 | if (this.scroll.current) { 18 | const { clientHeight } = this.scroll.current.getValues(); 19 | this.scroll.current.scrollTop(elementOffset - ((clientHeight - elementHeight) / 2)); 20 | } 21 | } 22 | 23 | public onScroll = () => { 24 | if (this.props.onScroll && this.scroll.current) { 25 | this.props.onScroll(); 26 | } 27 | } 28 | 29 | public scrollState = (): ScrollState => { 30 | return (this.scroll.current && this.scroll.current.getValues()) as ScrollState; 31 | } 32 | 33 | public render() { 34 | return ( 35 |
} 39 | renderThumbHorizontal={(props: any) =>
} 40 | onScroll={this.onScroll} 41 | > 42 | {this.props.children} 43 | 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/forms/Slider.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | 3 | .wrapper { 4 | background-color: $grey-light; 5 | border-radius: 10px; 6 | width: 34px; 7 | height: 20px; 8 | display: inline-flex; 9 | align-items: center; 10 | border: none; 11 | padding: 0; 12 | position: relative; 13 | 14 | &:hover { 15 | cursor: pointer; 16 | } 17 | } 18 | 19 | // common button 20 | .pointer { 21 | background-color: white; 22 | border-radius: 100%; 23 | height: 16px; 24 | width: 16px; 25 | margin: 2px; 26 | transition: all 0.5s ease-in-out; 27 | } 28 | 29 | // disabled 30 | .disabled { 31 | 32 | &.wrapper { 33 | cursor: default; 34 | } 35 | 36 | .pointer { 37 | background-color: #bbb; 38 | } 39 | } 40 | 41 | 42 | // blocked / unblocked 43 | 44 | @mixin blocked() { 45 | margin-left: 34px - 16px - 2px; 46 | background: white url("./switch-disabled.svg") no-repeat 14% center; 47 | } 48 | 49 | @mixin unblocked() { 50 | margin-left: 2px; 51 | background: white url("./switch-active.svg") no-repeat 84% center; 52 | } 53 | 54 | .pointerBlocked { 55 | @include blocked; 56 | } 57 | 58 | .pointerUnblocked { 59 | @include unblocked; 60 | } 61 | 62 | .moveOnHover:not(.disabled):hover { 63 | .pointerBlocked { 64 | @include unblocked; 65 | } 66 | .pointerUnblocked { 67 | @include blocked; 68 | } 69 | } 70 | 71 | .inProgress { 72 | background-image: none; 73 | } 74 | 75 | .progressIcon { 76 | position: absolute; 77 | } 78 | 79 | .progressBlocked { 80 | right: 4px; 81 | } 82 | 83 | .progressUnblocked { 84 | left: 4px; 85 | } -------------------------------------------------------------------------------- /src/utils/forms/Slider.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import { ProgressIcon } from '../icons/Icons'; 5 | import * as styles from './Slider.scss'; 6 | 7 | type SliderProps = 8 | React.ButtonHTMLAttributes 9 | & { disabled?: boolean, 10 | moveOnHover?: boolean, 11 | blocked: boolean, 12 | className?: string, 13 | inProgress?: boolean, 14 | }; 15 | 16 | export function Slider(props: SliderProps) { 17 | const { blocked, moveOnHover, inProgress, disabled, className, ...selectProps } = props; 18 | 19 | return ; 54 | } 55 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 51 | -------------------------------------------------------------------------------- /src/instant/instantDevModeHelpers.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { identity, of } from 'rxjs'; 3 | import { flatMap, map } from 'rxjs/operators'; 4 | import { Calls$ } from '../blockchain/calls/calls'; 5 | import { web3 } from '../blockchain/web3'; 6 | 7 | export function pluginDevModeHelpers(theCalls$: Calls$) { 8 | theCalls$.pipe( 9 | map(call => { 10 | (window as any).removeProxy = () => { 11 | call.proxyAddress().pipe( 12 | flatMap(proxyAddress => { 13 | if (!proxyAddress) { 14 | console.log('Proxy not found!'); 15 | return of(); 16 | } 17 | console.log('proxyAddress:', proxyAddress); 18 | return call.setOwner({ 19 | proxyAddress, 20 | ownerAddress: '0x0000000000000000000000000000000000000000' 21 | }); 22 | }) 23 | ).subscribe(identity); 24 | }; 25 | (window as any).disapproveProxy = (token: string) => { 26 | call.proxyAddress().pipe( 27 | flatMap(proxyAddress => { 28 | if (!proxyAddress) { 29 | console.log('Proxy not found!'); 30 | return of(); 31 | } 32 | console.log('proxyAddress:', proxyAddress); 33 | return call.disapproveProxy({ 34 | proxyAddress, token, 35 | gasPrice: new BigNumber(web3.eth.gasPrice.toString()), 36 | gasEstimation: 1000000 37 | }); 38 | }) 39 | ).subscribe(identity); 40 | }; 41 | console.log('Dev mode helpers installed!'); 42 | }) 43 | ).subscribe(identity); 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/modal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | export interface ModalProps { 5 | close: () => void; 6 | } 7 | 8 | export interface ModalOpenerProps { 9 | open: (modalClass: React.ComponentType) => void; 10 | } 11 | 12 | interface WrapperState { 13 | modalType?: React.ComponentType; 14 | } 15 | 16 | class ReRenderBarrier extends React.Component { 17 | public shouldComponentUpdate(): boolean { 18 | return false; 19 | } 20 | 21 | public render() { 22 | return this.props.children; 23 | } 24 | } 25 | 26 | export function withModal( 27 | Wrapped: React.ComponentType 28 | ): React.ComponentType { 29 | 30 | return class extends React.Component { 31 | constructor(o: O) { 32 | super(o); 33 | this.state = {}; 34 | } 35 | 36 | public render() { 37 | return 38 | 39 | }/> 40 | 41 | {this.state.modalType !== undefined && 42 | ReactDOM.createPortal( 43 |
44 | 45 |
, 46 | document.body)} 47 |
; 48 | } 49 | 50 | private open = (modalType: React.ComponentType) => { 51 | this.setState({ ...this.state, modalType }); 52 | } 53 | 54 | private close = (): void => { 55 | this.setState({ ...this.state, modalType: undefined }); 56 | } 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/forms/InputGroup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import classnames from 'classnames'; 4 | import * as styles from './InputGroup.scss'; 5 | 6 | type InputGroupProps = 7 | React.HTMLAttributes & 8 | { color?: 'red' | 'grey', 9 | sizer?: 'sm' | 'md' | 'lg' | 'unsized', 10 | disabled?: boolean, 11 | hasError?: boolean }; 12 | 13 | export const InputGroup = (props: InputGroupProps) => { 14 | const { children, className, color, sizer, hasError, disabled, ...otherProps } = props; 15 | return ( 16 |
24 | {children} 25 |
26 | ); 27 | }; 28 | 29 | export const InputGroupAddon = ({ children, className, border, ...props }: 30 | { children: any, 31 | className?: string, 32 | border: 'left' | 'right' | 'none' | 'both' 33 | } | any) => ( 34 |
38 | {children} 39 |
40 | ); 41 | -------------------------------------------------------------------------------- /src/utils/forms/switch-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/instant/asset/Asset.tsx: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import classnames from 'classnames'; 3 | import * as React from 'react'; 4 | 5 | import { tokens } from '../../blockchain/config'; 6 | import { User } from '../../blockchain/user'; 7 | import { formatAmountInstant } from '../../utils/formatters/format'; 8 | import { ProgressIcon } from '../../utils/icons/Icons'; 9 | import { Currency } from '../../utils/text/Text'; 10 | import * as styles from './Asset.scss'; 11 | 12 | export interface AssetProps { 13 | currency: string; 14 | balance?: any; 15 | onClick?: () => void; 16 | user?: User; 17 | isLocked?: boolean; 18 | } 19 | 20 | export class Asset extends React.Component { 21 | public render() { 22 | const { user, currency, onClick, isLocked } = this.props; 23 | const balance = user && user.account ? this.props.balance : new BigNumber(0); 24 | return ( 25 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/switchSpread.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subscription } from 'rxjs'; 2 | 3 | export function switchSpread( 4 | switchFn: (previousState: S, state: S) => Observable | undefined, 5 | spreadFn: (state: S, crumb: C | undefined) => S 6 | ) { 7 | return (source: Observable) => { 8 | return new Observable( 9 | subscriber => { 10 | let innerSub: Subscription | undefined; 11 | let previousCrumb: C | undefined; 12 | let previousState: S | undefined; 13 | 14 | source.subscribe({ 15 | next(state: S) { 16 | try { 17 | const result = switchFn(previousState || state, state); 18 | previousState = state; 19 | if (!result) { 20 | return subscriber.next(spreadFn(state, previousCrumb)); 21 | } 22 | const crumb$: Observable = result; 23 | 24 | if (innerSub) { 25 | innerSub.unsubscribe(); 26 | } 27 | innerSub = crumb$.subscribe({ 28 | next(innerCrumb) { 29 | subscriber.next(spreadFn(previousState!, innerCrumb)); 30 | previousCrumb = innerCrumb; 31 | }, 32 | error(err) { 33 | subscriber.error(err); 34 | } 35 | }); 36 | } catch (err) { 37 | subscriber.error(err); 38 | return; 39 | } 40 | 41 | }, 42 | error(err) { 43 | subscriber.error(err); 44 | }, 45 | complete() { 46 | subscriber.complete(); 47 | } 48 | }); 49 | } 50 | ); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/blockchain/calls/wrapUnwrapCalls.tsx: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import * as React from 'react'; 3 | import { Money } from '../../utils/formatters/Formatters'; 4 | import { NetworkConfig, tokens } from '../config'; 5 | import { amountToWei } from '../utils'; 6 | import { TransactionDef } from './callsHelpers'; 7 | import { TxMetaKind } from './txMeta'; 8 | 9 | export interface WrapUnwrapData { 10 | amount: BigNumber; 11 | gasPrice: BigNumber; 12 | gas?: number; 13 | } 14 | 15 | export const wrap: TransactionDef = { 16 | call: (_: WrapUnwrapData, context: NetworkConfig) => 17 | context.tokens.WETH.contract.deposit, 18 | options: ({ amount, gasPrice, gas }: WrapUnwrapData) => ({ 19 | gas, 20 | gasPrice: gasPrice.toString(), 21 | value: amountToWei(amount, 'ETH').toFixed(0), 22 | }), 23 | kind: TxMetaKind.wrap, 24 | prepareArgs: (_: WrapUnwrapData) => [], 25 | description: ({ amount }: WrapUnwrapData) => 26 | 27 | Wrap 28 | , 29 | descriptionIcon: () => tokens.ETH.iconCircle, 30 | }; 31 | 32 | export const unwrap: TransactionDef = { 33 | call: (_: WrapUnwrapData, context: NetworkConfig) => 34 | context.tokens.WETH.contract.withdraw, 35 | options: ({ gasPrice, gas }) => ({ gas, gasPrice: gasPrice.toString() }), 36 | kind: TxMetaKind.unwrap, 37 | prepareArgs: ({ amount }: WrapUnwrapData) => 38 | [amountToWei(amount, 'ETH').toFixed(0)], 39 | description: ({ amount }: WrapUnwrapData) => 40 | 41 | Unwrap 42 | , 43 | descriptionIcon: () => tokens.ETH.iconCircle, 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils/forms/Radio.scss: -------------------------------------------------------------------------------- 1 | @import "../Variables"; 2 | 3 | .radio { 4 | position: relative; 5 | display: block; 6 | 7 | input[type="radio"] 8 | //input[type="checkbox"] 9 | { 10 | display: none; 11 | margin-right: 0; 12 | margin-top: 0; 13 | position: relative; 14 | 15 | &[disabled], 16 | &[readonly] { 17 | & + span:before { 18 | border-color: $disabledColor; 19 | opacity: 1; // iOS fix for unreadable disabled content 20 | } 21 | 22 | & + span:after { 23 | background-color: $disabledColor; 24 | } 25 | & + span { 26 | color: lighten($disabledColor, 15%); 27 | } 28 | } 29 | 30 | $leftPosition: -30px; 31 | $topPosition: 2px; 32 | $bigSize: 16px; 33 | $smallSize: 8px; 34 | 35 | // own checkbox or radio border 36 | & + span:before { 37 | content: ""; 38 | position: absolute; 39 | left: $leftPosition; 40 | top: $topPosition; 41 | 42 | box-sizing: border-box; 43 | width: $bigSize; 44 | height: $bigSize; 45 | 46 | border: 1.49px solid white; 47 | background-color: transparent; 48 | } 49 | 50 | // checked 51 | &:checked + span:after { 52 | position: absolute; 53 | left: $leftPosition + ($bigSize - $smallSize)/2; 54 | top: $topPosition + ($bigSize - $smallSize)/2; 55 | z-index: 2; 56 | 57 | content: ''; 58 | width: $smallSize; 59 | height: $smallSize; 60 | 61 | background-color: white; 62 | border: none; 63 | } 64 | 65 | } 66 | 67 | input[type="radio"] { 68 | + span:before, 69 | + span:after { 70 | border-radius: 50%; 71 | } 72 | } 73 | } 74 | 75 | .hasError { 76 | background-color: red; 77 | } -------------------------------------------------------------------------------- /src/utils/table/Table.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import * as styles from './Table.scss'; 5 | 6 | export const Table = ({ children, align, className }: 7 | { children: any, 8 | align?: 'right' | 'left' | 'center', 9 | className?: any }) => ( 10 | 15 | {children} 16 |
17 | ); 18 | 19 | export type RowClickableProps = React.HTMLAttributes & { 20 | clickable: boolean, 21 | onClick?: () => void, 22 | children: any, 23 | highlighted?: boolean, 24 | }; 25 | 26 | // Required: 27 | // onClick - function to do on click 28 | // clickable - param if row is clickable; boolean or observable 29 | // Optional: additional custom className, props 30 | export const RowClickable = (props: RowClickableProps) => { 31 | const { children, onClick, clickable, className, highlighted, ...trProps } = props; 32 | return ( 33 | 42 | {children} 43 | 44 | ); 45 | }; 46 | 47 | export const RowHighlighted = (props: React.HTMLAttributes) => { 48 | const { children, className, ...trProps } = props; 49 | return ( 50 | 54 | {children} 55 | 56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /src/utils/price.test.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { calculateTradePrice } from './price'; 3 | 4 | test('(BUYING) DAI should be used as quote if both tokens are DAI and ETH', () => { 5 | const sell = new BigNumber(20); 6 | const buy = new BigNumber(5); 7 | const sellToken = 'ETH'; 8 | const buyToken = 'DAI'; 9 | 10 | const { price, quotation } = calculateTradePrice(sellToken, sell, buyToken, buy); 11 | 12 | expect(price).toEqual(new BigNumber(0.25)); 13 | expect(quotation).toEqual('ETH/DAI'); 14 | }); 15 | 16 | test('(SELLING) DAI should be used as quote if both tokens are DAI and ETH', () => { 17 | const sell = new BigNumber(20); 18 | const buy = new BigNumber(5); 19 | const sellToken = 'DAI'; 20 | const buyToken = 'ETH'; 21 | 22 | const { price, quotation } = calculateTradePrice(sellToken, sell, buyToken, buy); 23 | 24 | expect(price).toEqual(new BigNumber(4)); 25 | expect(quotation).toEqual('ETH/DAI'); 26 | }); 27 | 28 | test('(BUYING) ETH should be used as quote if DAI is not presented', () => { 29 | const sell = new BigNumber(20); 30 | const buy = new BigNumber(5); 31 | const sellToken = 'MKR'; 32 | const buyToken = 'ETH'; 33 | 34 | const { price, quotation } = calculateTradePrice(sellToken, sell, buyToken, buy); 35 | 36 | expect(price).toEqual(new BigNumber(0.25)); 37 | expect(quotation).toEqual('MKR/ETH'); 38 | }); 39 | 40 | test('(SELLING) ETH should be used as quote if DAI is not presented', () => { 41 | const sell = new BigNumber(20); 42 | const buy = new BigNumber(5); 43 | const sellToken = 'ETH'; 44 | const buyToken = 'MKR'; 45 | 46 | const { price, quotation } = calculateTradePrice(sellToken, sell, buyToken, buy); 47 | 48 | expect(price).toEqual(new BigNumber(4)); 49 | expect(quotation).toEqual('MKR/ETH'); 50 | }); 51 | -------------------------------------------------------------------------------- /src/utils/forms/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import * as React from 'react'; 3 | 4 | import * as styles from './ErrorMessage.scss'; 5 | 6 | type ErrorMessageProps = 7 | React.HTMLAttributes 8 | & { 9 | // array of error messages, sorted from most important: 10 | // the first one will be visible, others are shown on hover as list in given order 11 | messages: React.ReactChild[], 12 | }; 13 | 14 | export function ErrorMessage(props: ErrorMessageProps) { 15 | const { className, messages, ...otherProps } = props; 16 | return ( 17 |
`${title}\n${getInnerText(msg)}`, '') 20 | } 21 | {...otherProps} 22 | > 23 | { messages.length > 0 && messages[0] } 24 |
25 | ); 26 | } 27 | 28 | // https://gist.github.com/slavikshen/7b29b06215b9e7a08455 29 | // How to get inner text in React virtual DOM 30 | function getInnerText(obj: any): string { 31 | let buf = ''; 32 | if (obj) { 33 | const type = typeof(obj); 34 | if (type === 'string' || type === 'number') { 35 | buf += obj; 36 | } else if (type === 'object') { 37 | let children = null; 38 | if (Array.isArray(obj)) { 39 | children = obj; 40 | } else { 41 | const props = obj.props; 42 | if (props) { 43 | children = props.children; 44 | } 45 | } 46 | if (children) { 47 | if (Array.isArray(children)) { 48 | children.forEach((o) => { 49 | buf += getInnerText(o); 50 | }); 51 | } else { 52 | buf += getInnerText(children); 53 | } 54 | } 55 | } 56 | } 57 | return buf; 58 | } 59 | --------------------------------------------------------------------------------