├── .circleci
└── config.yml
├── .dockerignore
├── .editorconfig
├── .env.example
├── .eslintrc.json
├── .gitignore
├── .nvmrc
├── .prettierrc
├── CONFIG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── THEMES.md
├── package.json
├── public
├── _redirects
├── assets
│ └── icons
│ │ ├── dai.svg
│ │ ├── dgx.svg
│ │ ├── mkr.svg
│ │ ├── mln.svg
│ │ ├── rep.svg
│ │ ├── weth.svg
│ │ └── zrx.svg
├── favicons
│ ├── favicon-114.png
│ ├── favicon-120.png
│ ├── favicon-144.png
│ ├── favicon-152.png
│ ├── favicon-16.png
│ ├── favicon-256.png
│ ├── favicon-32.png
│ ├── favicon-48.png
│ ├── favicon-57.png
│ ├── favicon-64.png
│ ├── favicon-72.png
│ ├── favicon.html
│ └── favicon.ico
├── index.html
└── manifest.json
├── src
├── assets
│ └── icons
│ │ ├── ae.svg
│ │ ├── agi.svg
│ │ ├── ant.svg
│ │ ├── ast.svg
│ │ ├── bat.svg
│ │ ├── cvc.svg
│ │ ├── dai.svg
│ │ ├── dgd.svg
│ │ ├── dgx.svg
│ │ ├── dnt.svg
│ │ ├── erc20_logo.svg
│ │ ├── erc721_logo.svg
│ │ ├── fun.svg
│ │ ├── gno.svg
│ │ ├── gnt.svg
│ │ ├── install_metamask.svg
│ │ ├── knc.svg
│ │ ├── link.svg
│ │ ├── lpt.svg
│ │ ├── mana.svg
│ │ ├── mkr.svg
│ │ ├── mln.svg
│ │ ├── omg.svg
│ │ ├── powr.svg
│ │ ├── red_exclamation_sign.svg
│ │ ├── ren.svg
│ │ ├── rep.svg
│ │ ├── req.svg
│ │ ├── salt.svg
│ │ ├── snt.svg
│ │ ├── spank.svg
│ │ ├── wax.svg
│ │ ├── weth.svg
│ │ ├── zil.svg
│ │ └── zrx.svg
├── common
│ ├── config.ts
│ ├── configSchema.ts
│ ├── constants.ts
│ ├── markets.ts
│ └── tokens_meta_data.ts
├── components
│ ├── account
│ │ ├── index.ts
│ │ ├── wallet_connection_status.tsx
│ │ ├── wallet_connections_status_dot.tsx
│ │ ├── wallet_token_balances.tsx
│ │ ├── wallet_weth_balance.tsx
│ │ └── wallet_weth_modal.tsx
│ ├── app.tsx
│ ├── common
│ │ ├── adblock_detector.tsx
│ │ ├── big_number_input.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── card_base.tsx
│ │ ├── card_tab_selector.tsx
│ │ ├── centered_wrapper.tsx
│ │ ├── check_metamask_state_modal_container.tsx
│ │ ├── column_narrow.tsx
│ │ ├── column_wide.tsx
│ │ ├── dropdown.tsx
│ │ ├── dropdown_text_item.tsx
│ │ ├── empty_content.tsx
│ │ ├── error_card.tsx
│ │ ├── footer.tsx
│ │ ├── icons
│ │ │ ├── arrow_up_down_icon.tsx
│ │ │ ├── bell_icon.tsx
│ │ │ ├── chevron_down_icon.tsx
│ │ │ ├── chevron_right_icon.tsx
│ │ │ ├── close_icon.tsx
│ │ │ ├── close_modal_button.tsx
│ │ │ ├── filter_icon.tsx
│ │ │ ├── icon_metamask_large.tsx
│ │ │ ├── info_icon.tsx
│ │ │ ├── info_icon_full.tsx
│ │ │ ├── lock_icon.tsx
│ │ │ ├── magnifier_icon.tsx
│ │ │ ├── metamask_side_icon.tsx
│ │ │ ├── notification_cancel_icon.tsx
│ │ │ ├── notification_checkmark_icon.tsx
│ │ │ ├── outside_url_icon.tsx
│ │ │ ├── processing_icon.tsx
│ │ │ ├── radio_icon.tsx
│ │ │ ├── radio_icon_active.tsx
│ │ │ ├── sad_icon.tsx
│ │ │ ├── sort_icon.tsx
│ │ │ ├── token_icon.tsx
│ │ │ ├── view_all_icon.tsx
│ │ │ ├── warning_icon.tsx
│ │ │ └── warning_small_icon.tsx
│ │ ├── interval.tsx
│ │ ├── loading.tsx
│ │ ├── logo.tsx
│ │ ├── main_scrollable_wrapper.tsx
│ │ ├── metamask_error_modal.tsx
│ │ ├── pending_time.tsx
│ │ ├── show_number_with_colors.tsx
│ │ ├── spinner.tsx
│ │ ├── steps_modal
│ │ │ ├── base_step_modal.tsx
│ │ │ ├── buy_sell_collectible_step.tsx
│ │ │ ├── buy_sell_token_step.tsx
│ │ │ ├── sign_order_step.tsx
│ │ │ ├── step_pending_time.tsx
│ │ │ ├── steps_common.tsx
│ │ │ ├── steps_modal.tsx
│ │ │ ├── steps_progress.tsx
│ │ │ ├── toggle_token_lock_step.tsx
│ │ │ ├── unlock_collectibles_step.tsx
│ │ │ └── wrap_eth_step.tsx
│ │ ├── table.tsx
│ │ ├── toolbar.tsx
│ │ ├── tooltip.tsx
│ │ └── view_all.tsx
│ ├── erc20
│ │ ├── account
│ │ │ └── wallet_connection_content.tsx
│ │ ├── common
│ │ │ ├── content_wrapper.tsx
│ │ │ ├── markets_dropdown.tsx
│ │ │ └── toolbar_content.tsx
│ │ ├── erc20_app.tsx
│ │ ├── marketplace
│ │ │ ├── buy_sell.tsx
│ │ │ ├── cancel_order_button.tsx
│ │ │ ├── grid_row_spread.tsx
│ │ │ ├── order_book.tsx
│ │ │ ├── order_details.tsx
│ │ │ ├── order_history.tsx
│ │ │ └── wallet_balance.tsx
│ │ └── pages
│ │ │ ├── marketplace.tsx
│ │ │ └── my_wallet.tsx
│ ├── erc721
│ │ ├── account
│ │ │ └── wallet_connection_content.tsx
│ │ ├── collectibles
│ │ │ ├── collectible_card.tsx
│ │ │ ├── collectible_details_item_list.tsx
│ │ │ ├── collectible_details_item_tile.tsx
│ │ │ ├── collectible_details_list.tsx
│ │ │ ├── collectible_list_modal.tsx
│ │ │ ├── collectible_sell_modal.tsx
│ │ │ ├── collectibles_all.tsx
│ │ │ ├── collectibles_card_list.tsx
│ │ │ ├── collectibles_dropdown_button.tsx
│ │ │ ├── collectibles_dropdown_container.tsx
│ │ │ ├── collectibles_list.tsx
│ │ │ ├── collectibles_list_filter.tsx
│ │ │ ├── collectibles_list_sort.tsx
│ │ │ ├── collectibles_search.tsx
│ │ │ ├── owner_badge.tsx
│ │ │ └── price_badge.tsx
│ │ ├── common
│ │ │ ├── content_wrapper.tsx
│ │ │ ├── declining_price_graph.tsx
│ │ │ ├── input_search.tsx
│ │ │ └── toolbar_content.tsx
│ │ ├── erc721_app.tsx
│ │ ├── marketplace
│ │ │ ├── collectible_buy_sell.tsx
│ │ │ ├── collectible_description.tsx
│ │ │ ├── dutch_auction_price_chart_card.tsx
│ │ │ ├── marketplace_common.tsx
│ │ │ ├── sell_collectibles_button.tsx
│ │ │ └── trade_button.tsx
│ │ ├── modals
│ │ │ └── search-modal.tsx
│ │ └── pages
│ │ │ ├── all_collectibles.tsx
│ │ │ ├── individual_collectible.tsx
│ │ │ ├── list_collectibles.tsx
│ │ │ └── my_collectibles.tsx
│ ├── general_layout.tsx
│ └── notifications
│ │ ├── notification_item.tsx
│ │ └── notifications_dropdown.tsx
├── config.json
├── exceptions
│ ├── common.ts
│ ├── component_unmounted_exception.ts
│ ├── convert_balance_must_not_be_equal_exception.ts
│ ├── insufficient_eth_deposit_balance_exception.ts
│ ├── insufficient_fee_balance_exception.ts
│ ├── insufficient_orders_amount_exception.ts
│ ├── insufficient_token_balance_exception.ts
│ ├── relayer_exception.ts
│ ├── signature_failed_exception.ts
│ ├── signed_order_exception.ts
│ └── user_denied_transaction_exception.ts
├── global.d.ts
├── index.css
├── index.tsx
├── react-app-env.d.ts
├── serviceWorker.js
├── services
│ ├── collectibles_metadata_gateway.ts
│ ├── collectibles_metadata_sources
│ │ ├── index.ts
│ │ ├── mocked.ts
│ │ └── opensea.ts
│ ├── contract_wrappers.ts
│ ├── exchange.ts
│ ├── gas_price_estimation.ts
│ ├── local_storage.ts
│ ├── markets.ts
│ ├── orders.ts
│ ├── relayer.ts
│ ├── tokens.ts
│ └── web3_wrapper.ts
├── setupProxy.js
├── setupTests.js
├── store
│ ├── actions.ts
│ ├── blockchain
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── collectibles
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── index.ts
│ ├── market
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── middlewares.ts
│ ├── reducers.ts
│ ├── relayer
│ │ ├── actions.ts
│ │ └── reducers.ts
│ ├── router
│ │ └── actions.ts
│ ├── selectors.ts
│ └── ui
│ │ ├── actions.ts
│ │ └── reducers.ts
├── styled.d.ts
├── tests
│ ├── components
│ │ ├── account
│ │ │ ├── __snapshots__
│ │ │ │ └── wallet_token_balances.test.tsx.snap
│ │ │ └── wallet_token_balances.test.tsx
│ │ ├── common
│ │ │ ├── __snapshots__
│ │ │ │ ├── show_number_with_colors.test.tsx.snap
│ │ │ │ └── toolbar.test.tsx.snap
│ │ │ ├── big_number_input.test.tsx
│ │ │ ├── icons
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── token_icon.test.tsx.snap
│ │ │ │ └── token_icon.test.tsx
│ │ │ ├── markets_dropdown.test.tsx
│ │ │ ├── pending_time.test.tsx
│ │ │ ├── show_number_with_colors.test.tsx
│ │ │ └── toolbar.test.tsx
│ │ ├── erc20
│ │ │ └── marketplace
│ │ │ │ ├── __snapshots__
│ │ │ │ └── order_book.test.tsx.snap
│ │ │ │ ├── order_book.test.tsx
│ │ │ │ ├── order_details.test.tsx
│ │ │ │ └── wallet_balance.test.tsx
│ │ └── erc721
│ │ │ ├── collectibles
│ │ │ ├── __snapshots__
│ │ │ │ └── owner_badge.test.tsx.snap
│ │ │ └── owner_badge.test.tsx
│ │ │ └── marketplace
│ │ │ └── trade_button.test.tsx
│ ├── services
│ │ └── collectibles_metadata_gateways.test.ts
│ ├── store
│ │ ├── actions
│ │ │ └── blockchain.test.ts
│ │ └── selectors.test.ts
│ └── util
│ │ ├── known_tokens.test.ts
│ │ ├── logger.test.ts
│ │ ├── markets.test.ts
│ │ ├── notifications.test.ts
│ │ ├── number_utils.test.ts
│ │ ├── orders.test.ts
│ │ ├── steps.test.ts
│ │ ├── steps_modals_generation.test.ts
│ │ ├── test_with_theme.tsx
│ │ ├── tokens.test.ts
│ │ └── ui_orders.test.ts
├── themes
│ ├── commons.ts
│ ├── dark_theme.ts
│ ├── default_theme.ts
│ ├── theme_meta_data.ts
│ └── theme_meta_data_utils.ts
└── util
│ ├── cancelable_promises.ts
│ ├── collectibles.ts
│ ├── error_messages.ts
│ ├── filterable_collectibles.ts
│ ├── known_tokens.ts
│ ├── logger.ts
│ ├── markets.ts
│ ├── notifications.ts
│ ├── number_utils.ts
│ ├── orders.ts
│ ├── sleep.ts
│ ├── sortable_collectibles.ts
│ ├── steps.ts
│ ├── steps_modals_generation.ts
│ ├── test-utils.ts
│ ├── time_utils.ts
│ ├── token_meta_data.ts
│ ├── tokens.ts
│ ├── transaction_link.ts
│ ├── transactions.ts
│ ├── types.ts
│ └── ui_orders.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/node:10
6 | steps:
7 | - checkout
8 | - restore_cache: # special step to restore the dependency cache
9 | name: Restore Yarn Package Cache
10 | keys:
11 | - yarn-packages-{{ checksum "yarn.lock" }}
12 | - run:
13 | name: Setup Yarn Dependencies #install all the dependencies
14 | command: yarn install --frozen-lockfile
15 | - save_cache: #saves a cache of the installed dependencies
16 | name: Save Yarn Package Cache
17 | key: yarn-packages-{{ checksum "yarn.lock" }}
18 | paths:
19 | - ~/.cache/yarn
20 | - run:
21 | name: Lint checks
22 | command: yarn run lint
23 | - run:
24 | name: Tests
25 | command: yarn run test-once --maxWorkers 2
26 | - run:
27 | name: Coveralls
28 | command: COVERALLS_REPO_TOKEN=${COVERALLS_REPO_TOKEN} && yarn run coveralls
29 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | node_modules
3 | build
4 | /coverage
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # All files
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | charset = utf-8
11 | indent_style = space
12 | indent_size = 4
13 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_RELAYER_URL='http://localhost:3000/v3'
2 | REACT_APP_RELAYER_WS_URL='ws://localhost:3000/'
3 | REACT_APP_NETWORK_ID=50
4 | REACT_APP_CHAIN_ID=1337
5 | REACT_APP_FEE_PERCENTAGE='0'
6 | REACT_APP_FEE_RECIPIENT='0x0000000000000000000000000000000000000000'
7 | REACT_APP_DEFAULT_ORDER_EXPIRY_SECONDS=86400
8 | REACT_APP_NOTIFICATIONS_LIMIT=20
9 | REACT_APP_START_BLOCK_LIMIT=100000
10 | # 0 is disabled
11 | REACT_APP_UI_UPDATE_CHECK_INTERVAL=5000
12 | # 0 is disabled
13 | REACT_APP_UPDATE_ETHER_PRICE_INTERVAL=3600000
14 | REACT_APP_LOGGER_ID='0x-launch-kit-frontend'
15 | REACT_APP_STEP_MODAL_DONE_STATUS_VISIBILITY_TIME='3500'
16 | REACT_APP_COLLECTIBLES_SOURCE='opensea'
17 | REACT_APP_OPENSEA_API_KEY='abcdef0123456789abcdef0123456789'
18 | # Axie mainnet address
19 | REACT_APP_COLLECTIBLE_ADDRESS='0xf5b0a3efb8e8e4c201e2a935f110eaaf3ffecb8d'
20 | REACT_APP_COLLECTIBLE_NAME='CryptoKitties'
21 | REACT_APP_COLLECTIBLE_DESCRIPTION='CryptoKitties is a state of the art blockchain-based game, enabling users to trade and sell their cards freely, with the same level of ownership as if they were real, tangible cards.'
22 | REACT_APP_ERC20_THEME_NAME = 'DARK_THEME'
23 | REACT_APP_ERC721_THEME_NAME = 'LIGHT_THEME'
24 | REACT_APP_DEFAULT_BASE_PATH='/erc721'
25 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | #Webstorm
27 | .idea
28 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v10.15
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "printWidth": 120,
4 | "trailingComma": all,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12-alpine as yarn-install
2 | WORKDIR /app
3 | COPY package.json yarn.lock ./
4 | RUN apk update && \
5 | apk upgrade && \
6 | apk add --no-cache --virtual build-dependencies bash git openssh python make g++ && \
7 | yarn --no-cache || \
8 | apk del build-dependencies && \
9 | yarn cache clean
10 |
11 | # Stage 1
12 | FROM node:12-alpine as react-build
13 | WORKDIR /app
14 | COPY --from=yarn-install /app/node_modules /app/node_modules
15 | COPY . .
16 | RUN yarn build
17 |
18 | # Stage 2 - the production environment
19 | FROM nginx:alpine
20 | COPY --from=react-build /app/build /usr/share/nginx/html
21 | EXPOSE 80
22 | CMD ["nginx", "-g", "daemon off;"]
23 |
--------------------------------------------------------------------------------
/THEMES.md:
--------------------------------------------------------------------------------
1 | # Custom themes guide
2 |
3 | ## How to add new themes
4 |
5 | By default the app uses a `LIGHT THEME` and there is also available a `DARK THEME`.
6 |
7 | If you want to customize the app with a custom theme you can add your own.
8 |
9 | ### How to create a new theme
10 |
11 | Note: All the relevant files are in the `src/themes` folder.
12 |
13 | For this example our new theme will be called `NEW THEME`.
14 |
15 | 1. Copy default_theme.ts to new_theme.ts
16 | 2. Change `export class DefaultTheme` to `export class NewTheme` in `new_theme.ts`
17 | 3. Add:
18 | `{ name: 'NEW_THEME', theme: new NewTheme(), },`
19 | in the `KNOWN_THEMES_META_DATA` array found in `themes/theme_meta_data.ts`
20 |
21 | 4. In your `.env` file change the value of `REACT_APP_THEME_NAME` from `DEFAULT_THEME` to `NEW_THEME`
22 |
23 | #### Dark Theme example
24 |
25 | dark_theme.ts:
26 |
27 | ```typescript
28 | const modalThemeStyle: ThemeModalStyle = {
29 | content: {
30 | ......
31 | },
32 | overlay: {
33 | ......
34 | },
35 | };
36 |
37 | const darkThemeColors: ThemeProperties = {
38 | background: '#000',
39 | borderColor: '#5A5A5A',
40 | boxShadow: '0 10px 10px rgba(0, 0, 0, 0.1)',
41 | buttonConvertBackgroundColor: '#343434',
42 | buttonConvertBorderColor: '#000',
43 | buttonConvertTextColor: '#fff',
44 | .......
45 | };
46 | export class DarkTheme extends DefaultTheme {
47 | constructor() {
48 | super();
49 | this.componentsTheme = darkThemeColors;
50 | this.modalTheme = modalThemeStyle;
51 | }
52 | }
53 | ```
54 |
55 | theme_meta_data.ts object:
56 |
57 | ```typescript
58 | export const KNOWN_THEMES_META_DATA: ThemeMetaData[] = [
59 | {
60 | name: 'DEFAULT_THEME',
61 | theme: new DefaultTheme(),
62 | },
63 | {
64 | name: 'DARK_THEME',
65 | theme: new DarkTheme(),
66 | },
67 | ];
68 | ```
69 |
70 | .env file:
71 |
72 | ```
73 | ...
74 | REACT_APP_ERC20_THEME_NAME = 'DARK_THEME'
75 | REACT_APP_ERC721_THEME_NAME = 'LIGHT_THEME'
76 |
77 | ```
78 |
79 | Note: you can configure the themes for the ERC20 and ERC721 app separately using those env vars.
80 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/assets/icons/dai.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/assets/icons/dgx.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/assets/icons/mkr.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/assets/icons/mln.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/assets/icons/rep.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/public/assets/icons/weth.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/assets/icons/zrx.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/favicons/favicon-114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-114.png
--------------------------------------------------------------------------------
/public/favicons/favicon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-120.png
--------------------------------------------------------------------------------
/public/favicons/favicon-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-144.png
--------------------------------------------------------------------------------
/public/favicons/favicon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-152.png
--------------------------------------------------------------------------------
/public/favicons/favicon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-16.png
--------------------------------------------------------------------------------
/public/favicons/favicon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-256.png
--------------------------------------------------------------------------------
/public/favicons/favicon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-32.png
--------------------------------------------------------------------------------
/public/favicons/favicon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-48.png
--------------------------------------------------------------------------------
/public/favicons/favicon-57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-57.png
--------------------------------------------------------------------------------
/public/favicons/favicon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-64.png
--------------------------------------------------------------------------------
/public/favicons/favicon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon-72.png
--------------------------------------------------------------------------------
/public/favicons/favicon.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xProject/0x-launch-kit-frontend/44272c9c22ed0e9e4cbdcdb880cfaffd086905cd/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 | 0x Launch Kit Frontend
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/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": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/icons/ae.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/agi.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/ant.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/ast.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/bat.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/cvc.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/dai.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/dgd.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/dgx.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/dnt.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/gno.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/gnt.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/knc.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/link.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/lpt.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/icons/mkr.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/mln.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/powr.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/red_exclamation_sign.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/icons/ren.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/icons/rep.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/assets/icons/req.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/salt.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/snt.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/spank.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/wax.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/weth.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/icons/zil.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/zrx.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/common/config.ts:
--------------------------------------------------------------------------------
1 | import { Validator } from 'jsonschema';
2 |
3 | import configFile from '../config.json';
4 | import { ConfigFile } from '../util/types';
5 |
6 | import { configSchema, schemas } from './configSchema';
7 |
8 | export class Config {
9 | private static _instance: Config;
10 | private readonly _validator: Validator;
11 | private readonly _config: ConfigFile;
12 | public static getInstance(): Config {
13 | if (!Config._instance) {
14 | Config._instance = new Config();
15 | }
16 | return Config._instance;
17 | }
18 | public static getConfig(): ConfigFile {
19 | return this.getInstance()._config;
20 | }
21 | constructor() {
22 | this._validator = new Validator();
23 | for (const schema of schemas) {
24 | this._validator.addSchema(schema, schema.id);
25 | }
26 | this._validator.validate(configFile, configSchema, { throwError: true });
27 | this._config = configFile;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/common/configSchema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'jsonschema';
2 |
3 | export const configSchema: Schema = {
4 | id: '/configSchema',
5 | properties: {
6 | tokens: { type: 'array', items: { $ref: '/tokenMetaDataSchema' } },
7 | pairs: { type: 'array', items: { $ref: '/pairSchema' } },
8 | },
9 | required: ['tokens', 'pairs'],
10 | type: 'object',
11 | };
12 |
13 | export const schemas: Schema[] = [
14 | {
15 | id: '/addressSchema',
16 | type: 'string',
17 | pattern: '^0x[0-9a-f]{40}$',
18 | },
19 | {
20 | id: '/wholeNumberSchema',
21 | anyOf: [
22 | {
23 | type: 'string',
24 | pattern: '^\\d+$',
25 | },
26 | {
27 | type: 'integer',
28 | },
29 | ],
30 | },
31 | {
32 | id: '/marketFilterSchema',
33 | properties: {
34 | text: { type: 'string' },
35 | valute: { type: 'string' },
36 | },
37 | required: ['text', 'value'],
38 | type: 'object',
39 | },
40 | {
41 | id: '/pairSchema',
42 | properties: {
43 | base: { type: 'string' },
44 | quote: { type: 'string' },
45 | },
46 | required: ['base', 'quote'],
47 | type: 'object',
48 | },
49 | {
50 | id: '/tokenMetaDataSchema',
51 | properties: {
52 | symbol: { type: 'string' },
53 | name: { type: 'string' },
54 | icon: { type: 'string' },
55 | primaryColor: { type: 'string' },
56 | decimals: { $ref: '/wholeNumberSchema' },
57 | displayDecimals: { $ref: '/wholeNumberSchema' },
58 | },
59 | required: ['decimals', 'symbol', 'name', 'addresses'],
60 | type: 'object',
61 | },
62 | {
63 | id: '/configSchema',
64 | properties: {
65 | tokens: { type: 'array', items: { $ref: '/tokenMetaDataSchema' } },
66 | pairs: { type: 'array', items: { $ref: '/pairSchema' } },
67 | marketFilters: { type: 'array', items: { $ref: '/marketFilterSchema' } },
68 | },
69 | required: ['tokens', 'pairs'],
70 | type: 'object',
71 | },
72 | ];
73 |
--------------------------------------------------------------------------------
/src/common/markets.ts:
--------------------------------------------------------------------------------
1 | import { Filter } from '../util/types';
2 |
3 | import { Config } from './config';
4 |
5 | export const availableMarkets = Config.getConfig().pairs;
6 | const allFilter = {
7 | text: 'ALL',
8 | value: null,
9 | };
10 | const suppliedMarketFilters = Config.getConfig().marketFilters;
11 | export const marketFilters: Filter[] = suppliedMarketFilters ? [allFilter, ...suppliedMarketFilters] : [];
12 |
--------------------------------------------------------------------------------
/src/common/tokens_meta_data.ts:
--------------------------------------------------------------------------------
1 | import { Config } from './config';
2 |
3 | export interface TokenMetaData {
4 | addresses: { [key: number]: string };
5 | symbol: string;
6 | decimals: number;
7 | name: string;
8 | primaryColor: string;
9 | icon?: string;
10 | displayDecimals?: number;
11 | }
12 | export const KNOWN_TOKENS_META_DATA: TokenMetaData[] = Config.getConfig().tokens;
13 |
--------------------------------------------------------------------------------
/src/components/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './wallet_token_balances';
2 | export * from './wallet_weth_balance';
3 |
--------------------------------------------------------------------------------
/src/components/account/wallet_connection_status.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { Dropdown, DropdownPositions } from '../common/dropdown';
5 | import { ChevronDownIcon } from '../common/icons/chevron_down_icon';
6 |
7 | import { WalletConnectionStatusDot } from './wallet_connections_status_dot';
8 |
9 | const WalletConnectionStatusWrapper = styled.div`
10 | align-items: center;
11 | cursor: pointer;
12 | display: flex;
13 | `;
14 |
15 | const WalletConnectionStatusDotStyled = styled(WalletConnectionStatusDot)`
16 | margin-right: 10px;
17 | `;
18 |
19 | const WalletConnectionStatusText = styled.span`
20 | color: ${props => props.theme.componentsTheme.textColorCommon};
21 | font-feature-settings: 'calt' 0;
22 | font-size: 16px;
23 | font-weight: 500;
24 | margin-right: 10px;
25 | `;
26 |
27 | interface OwnProps extends HTMLAttributes {
28 | walletConnectionContent: React.ReactNode;
29 | shouldCloseDropdownOnClickOutside?: boolean;
30 | headerText: string;
31 | ethAccount: string;
32 | }
33 |
34 | type Props = OwnProps;
35 |
36 | export class WalletConnectionStatusContainer extends React.PureComponent {
37 | public render = () => {
38 | const {
39 | headerText,
40 | walletConnectionContent,
41 | ethAccount,
42 | shouldCloseDropdownOnClickOutside,
43 | ...restProps
44 | } = this.props;
45 | const status: string = ethAccount ? 'active' : '';
46 | const header = (
47 |
48 |
49 | {headerText}
50 |
51 |
52 | );
53 |
54 | const body = <>{walletConnectionContent}>;
55 | return (
56 |
63 | );
64 | };
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/account/wallet_connections_status_dot.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface StatusProps {
4 | status?: string;
5 | }
6 |
7 | export const WalletConnectionStatusDot = styled.div`
8 | background-color: ${props => (props.status ? '#55BC65' : '#ccc')};
9 | border-radius: 50%;
10 | height: 10px;
11 | width: 10px;
12 | `;
13 |
--------------------------------------------------------------------------------
/src/components/common/card.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeDimensions } from '../../themes/commons';
5 |
6 | import { CardBase } from './card_base';
7 |
8 | interface Props extends HTMLAttributes {
9 | title?: string;
10 | action?: React.ReactNode;
11 | children: React.ReactNode;
12 | minHeightBody?: string;
13 | }
14 |
15 | const CardWrapper = styled(CardBase)`
16 | display: flex;
17 | flex-direction: column;
18 | margin-bottom: ${themeDimensions.verticalSeparationSm};
19 | max-height: 100%;
20 |
21 | &:last-child {
22 | margin-bottom: 0;
23 | }
24 | `;
25 |
26 | const CardHeader = styled.div`
27 | align-items: center;
28 | border-bottom: 1px solid ${props => props.theme.componentsTheme.cardBorderColor};
29 | display: flex;
30 | flex-grow: 0;
31 | flex-shrink: 0;
32 | justify-content: space-between;
33 | padding: ${themeDimensions.verticalPadding} ${themeDimensions.horizontalPadding};
34 | `;
35 |
36 | const CardTitle = styled.h1`
37 | color: ${props => props.theme.componentsTheme.cardTitleColor};
38 | font-size: 16px;
39 | font-style: normal;
40 | font-weight: 600;
41 | line-height: 1.2;
42 | margin: 0;
43 | padding: 0 20px 0 0;
44 | `;
45 |
46 | const CardBody = styled.div<{ minHeightBody?: string }>`
47 | margin: 0;
48 | min-height: ${props => props.minHeightBody};
49 | overflow-x: auto;
50 | padding: ${themeDimensions.verticalPadding} ${themeDimensions.horizontalPadding};
51 | position: relative;
52 | `;
53 |
54 | CardBody.defaultProps = {
55 | minHeightBody: '140px',
56 | };
57 |
58 | export const Card: React.FC = props => {
59 | const { title, action, children, minHeightBody, ...restProps } = props;
60 |
61 | return (
62 |
63 | {title || action ? (
64 |
65 | {title}
66 | {action ? action : null}
67 |
68 | ) : null}
69 | {children}
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/src/components/common/card_base.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeDimensions } from '../../themes/commons';
5 |
6 | interface Props extends HTMLAttributes {
7 | children: React.ReactNode;
8 | }
9 |
10 | const CardWrapper = styled.div`
11 | background-color: ${props => props.theme.componentsTheme.cardBackgroundColor};
12 | border-radius: ${themeDimensions.borderRadius};
13 | border: 1px solid ${props => props.theme.componentsTheme.cardBorderColor};
14 | `;
15 |
16 | export const CardBase: React.FC = props => {
17 | const { children, ...restProps } = props;
18 |
19 | return {children};
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/common/card_tab_selector.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { TabItem } from '../../util/types';
5 |
6 | interface Props extends HTMLAttributes {
7 | tabs: TabItem[];
8 | }
9 |
10 | interface ItemProps {
11 | active?: boolean;
12 | }
13 |
14 | const CardTabSelectorWrapper = styled.div`
15 | align-items: center;
16 | color: ${props => props.theme.componentsTheme.lightGray};
17 | display: flex;
18 | font-size: 14px;
19 | font-weight: 500;
20 | justify-content: space-between;
21 | line-height: 1.2;
22 | `;
23 |
24 | const CardTabSelectorItem = styled.span`
25 | color: ${props =>
26 | props.active ? props.theme.componentsTheme.textColorCommon : props.theme.componentsTheme.lightGray};
27 | cursor: ${props => (props.active ? 'default' : 'pointer')};
28 | user-select: none;
29 | `;
30 |
31 | const CardTabSelectorItemSeparator = styled.span`
32 | color: #dedede;
33 | cursor: default;
34 | font-weight: 400;
35 | padding: 0 5px;
36 | user-select: none;
37 |
38 | &:last-child {
39 | display: none;
40 | }
41 | `;
42 |
43 | export const CardTabSelector: React.FC = props => {
44 | const { tabs, ...restProps } = props;
45 |
46 | return (
47 |
48 | {tabs.map((item, index) => {
49 | return (
50 |
51 |
52 | {item.text}
53 |
54 | /
55 |
56 | );
57 | })}
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/components/common/centered_wrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints } from '../../themes/commons';
5 |
6 | interface Props extends HTMLAttributes {
7 | children: React.ReactNode;
8 | }
9 |
10 | const Centered = styled.div`
11 | display: flex;
12 | flex-direction: column;
13 | flex-grow: 1;
14 | margin: 0 auto;
15 | max-width: 100%;
16 | width: ${themeBreakPoints.xxl};
17 | `;
18 |
19 | export const CenteredWrapper: React.FC = props => {
20 | const { children, ...restProps } = props;
21 |
22 | return {children};
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/common/column_narrow.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints, themeDimensions } from '../../themes/commons';
5 |
6 | interface Props extends HTMLAttributes {
7 | children: React.ReactNode;
8 | }
9 |
10 | const ColumnNarrowWrapper = styled.div`
11 | display: flex;
12 | flex-direction: column;
13 | flex-shrink: 0;
14 | max-width: 100%;
15 | width: 100%;
16 |
17 | @media (min-width: ${themeBreakPoints.xl}) {
18 | min-width: ${themeDimensions.sidebarWidth};
19 | width: ${themeDimensions.sidebarWidth};
20 | }
21 |
22 | &:first-child {
23 | @media (min-width: ${themeBreakPoints.xl}) {
24 | margin-right: 10px;
25 | }
26 | }
27 |
28 | &:last-child {
29 | @media (min-width: ${themeBreakPoints.xl}) {
30 | margin-left: 10px;
31 | }
32 | }
33 | `;
34 |
35 | export const ColumnNarrow: React.FC = props => {
36 | const { children, ...restProps } = props;
37 |
38 | return {children};
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/common/column_wide.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints } from '../../themes/commons';
5 |
6 | interface Props extends HTMLAttributes {
7 | children: React.ReactNode;
8 | }
9 |
10 | const ColumnWideWrapper = styled.div`
11 | flex-grow: 0;
12 | flex-shrink: 1;
13 | max-width: 100%;
14 | min-width: 0;
15 |
16 | @media (min-width: ${themeBreakPoints.xl}) {
17 | flex-grow: 1;
18 | }
19 |
20 | &:first-child {
21 | @media (min-width: ${themeBreakPoints.xl}) {
22 | margin-right: 10px;
23 | }
24 | }
25 |
26 | &:last-child {
27 | @media (min-width: ${themeBreakPoints.xl}) {
28 | margin-left: 10px;
29 | }
30 | }
31 | `;
32 |
33 | export const ColumnWide: React.FC = props => {
34 | const { children, ...restProps } = props;
35 |
36 | return {children};
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/common/dropdown_text_item.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeDimensions } from '../../themes/commons';
5 |
6 | interface Props extends HTMLAttributes {
7 | active?: boolean;
8 | onClick?: any;
9 | text: string;
10 | }
11 |
12 | export const DropdownTextItemWrapper = styled.div<{ active?: boolean }>`
13 | background-color: ${props =>
14 | props.active ? props.theme.componentsTheme.rowActive : props.theme.componentsTheme.dropdownBackgroundColor};
15 | border-bottom: 1px solid ${props => props.theme.componentsTheme.dropdownBorderColor};
16 | color: ${props => props.theme.componentsTheme.dropdownTextColor};
17 | cursor: pointer;
18 | font-size: 14px;
19 | font-weight: normal;
20 | line-height: 1.3;
21 | padding: 12px ${themeDimensions.horizontalPadding};
22 |
23 | &:hover {
24 | background-color: ${props => props.theme.componentsTheme.rowActive};
25 | }
26 |
27 | &:first-child {
28 | border-top-left-radius: ${themeDimensions.borderRadius};
29 | border-top-right-radius: ${themeDimensions.borderRadius};
30 | }
31 |
32 | &:last-child {
33 | border-bottom-left-radius: ${themeDimensions.borderRadius};
34 | border-bottom-right-radius: ${themeDimensions.borderRadius};
35 | border-bottom: none;
36 | }
37 | `;
38 |
39 | export const DropdownTextItem: React.FC = props => {
40 | const { text, onClick, ...restProps } = props;
41 |
42 | return (
43 |
44 | {text}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/components/common/empty_content.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | interface EmptyWrapperProps {
5 | alignAbsoluteCenter?: boolean;
6 | }
7 |
8 | interface Props extends HTMLAttributes, EmptyWrapperProps {
9 | text: string;
10 | }
11 |
12 | const EmptyContentWrapper = styled.div`
13 | align-items: center;
14 | color: ${props => props.theme.componentsTheme.lightGray};
15 | display: flex;
16 | font-size: 16px;
17 | font-weight: 500;
18 | height: 100%;
19 | justify-content: center;
20 | width: 100%;
21 |
22 | ${props =>
23 | props.alignAbsoluteCenter
24 | ? `
25 | bottom: 0;
26 | left: 0;
27 | position: absolute;
28 | right: 0;
29 | top: 0;
30 | `
31 | : ''}
32 | `;
33 |
34 | export const EmptyContent: React.FC = props => {
35 | const { text, ...restProps } = props;
36 |
37 | return {text};
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/common/error_card.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeDimensions } from '../../themes/commons';
5 |
6 | import { LockIcon } from './icons/lock_icon';
7 | import { MetamaskSideIcon } from './icons/metamask_side_icon';
8 | import { SadIcon } from './icons/sad_icon';
9 | import { WarningIcon } from './icons/warning_icon';
10 |
11 | interface Props extends HTMLAttributes, ErrorProps {
12 | text: string;
13 | }
14 |
15 | interface ErrorProps {
16 | fontSize?: FontSize;
17 | icon?: ErrorIcons;
18 | textAlign?: string;
19 | }
20 |
21 | export enum ErrorIcons {
22 | Lock = 1,
23 | Sad = 2,
24 | Metamask = 3,
25 | Warning = 4,
26 | }
27 |
28 | export enum FontSize {
29 | Large = 1,
30 | Medium = 2,
31 | }
32 |
33 | const ErrorCardContainer = styled.div`
34 | align-items: center;
35 | background-color: ${props => props.theme.componentsTheme.errorCardBackground};
36 | border-radius: ${themeDimensions.borderRadius};
37 | border: 1px solid ${props => props.theme.componentsTheme.errorCardBorder};
38 | color: ${props => props.theme.componentsTheme.errorCardText};
39 | display: flex;
40 | font-size: ${props => (props.fontSize === FontSize.Large ? '16px' : '14px')};
41 | line-height: 1.2;
42 | padding: 10px 15px;
43 | ${props => (props.textAlign === 'center' ? 'justify-content: center;' : '')}
44 | `;
45 |
46 | const IconContainer = styled.span`
47 | margin-right: 10px;
48 | `;
49 |
50 | const getIcon = (icon: ErrorIcons) => {
51 | let theIcon: any;
52 |
53 | if (icon === ErrorIcons.Lock) {
54 | theIcon = ;
55 | }
56 | if (icon === ErrorIcons.Metamask) {
57 | theIcon = ;
58 | }
59 | if (icon === ErrorIcons.Sad) {
60 | theIcon = ;
61 | }
62 | if (icon === ErrorIcons.Warning) {
63 | theIcon = ;
64 | }
65 |
66 | return {theIcon};
67 | };
68 |
69 | export const ErrorCard: React.FC = props => {
70 | const { text, icon, ...restProps } = props;
71 | const errorIcon = icon ? getIcon(icon) : null;
72 |
73 | return (
74 |
75 | {errorIcon}
76 | {text}
77 |
78 | );
79 | };
80 |
--------------------------------------------------------------------------------
/src/components/common/icons/arrow_up_down_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const ArrowUpDownIcon = () => {
4 | return (
5 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/components/common/icons/bell_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const BellIcon = () => (
4 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/common/icons/chevron_down_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const ChevronDownIcon = () => {
4 | return (
5 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/src/components/common/icons/chevron_right_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const ChevronRight = () => {
4 | return (
5 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/src/components/common/icons/close_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const CloseIcon = () => {
4 | return (
5 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/common/icons/close_modal_button.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | const CloseButton = styled.span`
5 | align-items: center;
6 | cursor: pointer;
7 | display: flex;
8 | height: 100%;
9 | justify-content: center;
10 | width: 20px;
11 | `;
12 |
13 | const CloseButtonContainer = styled.div`
14 | align-items: center;
15 | display: flex;
16 | height: 20px;
17 | justify-content: flex-end;
18 | margin-right: -10px;
19 | margin-top: -10px;
20 | `;
21 |
22 | const CloseButtonSVG = () => {
23 | return (
24 |
32 | );
33 | };
34 |
35 | interface Props extends HTMLAttributes {
36 | onClick?: any;
37 | }
38 |
39 | export const CloseModalButton: React.FC = props => {
40 | const { onClick, ...restProps } = props;
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/common/icons/filter_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const FilterIcon = () => (
4 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/common/icons/info_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const InfoIcon = () => {
4 | return (
5 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/common/icons/info_icon_full.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const InfoIconFull = () => {
4 | return (
5 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/common/icons/lock_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const LockIcon = () => (
4 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/common/icons/magnifier_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const MagnifierIcon = () => {
4 | return (
5 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/components/common/icons/notification_cancel_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const NotificationCancelIcon = () => {
4 | return (
5 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/common/icons/notification_checkmark_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const NotificationCheckmarkIcon = () => {
4 | return (
5 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/components/common/icons/outside_url_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const OutsideUrlIcon = () => {
4 | return (
5 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/common/icons/processing_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const ProcessingIcon = () => {
4 | return (
5 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/components/common/icons/radio_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const RadioIcon = () => (
4 |
7 | );
8 |
--------------------------------------------------------------------------------
/src/components/common/icons/radio_icon_active.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const RadioIconActive = () => (
4 |
12 | );
13 |
--------------------------------------------------------------------------------
/src/components/common/icons/sort_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const SortIcon = () => (
4 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/common/icons/token_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactSVG from 'react-svg';
3 | import styled, { withTheme } from 'styled-components';
4 |
5 | import { Theme } from '../../../themes/commons';
6 |
7 | interface Props {
8 | symbol: string;
9 | primaryColor?: string;
10 | isInline?: boolean;
11 | icon?: string;
12 | theme: Theme;
13 | }
14 |
15 | const IconContainer = styled.div<{ color: string; isInline?: boolean }>`
16 | align-items: center;
17 | background-color: ${props => (props.color ? props.color : 'transparent')};
18 | border-radius: 50%;
19 | display: ${props => (props.isInline ? 'inline-flex' : 'flex')};
20 | height: 26px;
21 | justify-content: center;
22 | width: 26px;
23 | `;
24 |
25 | const Label = styled.label`
26 | color: #fff;
27 | font-size: 0.7em;
28 | font-weight: 500;
29 | line-height: normal;
30 | margin: 0;
31 | `;
32 |
33 | const TokenIconContainer = (props: Props) => {
34 | const { symbol, primaryColor, theme, icon, ...restProps } = props;
35 | const fallBack = ;
36 | const Icon =
37 | // tslint:disable-next-line:jsx-no-lambda
38 | icon ? fallBack} /> : fallBack;
39 | return (
40 |
41 | {Icon}
42 |
43 | );
44 | };
45 |
46 | const TokenIcon = withTheme(TokenIconContainer);
47 |
48 | export { TokenIcon };
49 |
--------------------------------------------------------------------------------
/src/components/common/icons/view_all_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const ViewAllIcon = () => {
4 | return (
5 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/src/components/common/icons/warning_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const WarningIcon = () => (
4 |
9 | );
10 |
--------------------------------------------------------------------------------
/src/components/common/icons/warning_small_icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const WarningSmallIcon = () => (
4 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/common/interval.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface Props {
4 | delay: number;
5 | children: (now: Date) => React.ReactNode;
6 | }
7 |
8 | export class Interval extends React.Component {
9 | private _interval: any = null;
10 |
11 | public componentDidMount = () => {
12 | this._interval = setInterval(() => this.forceUpdate(), this.props.delay);
13 | };
14 |
15 | public componentWillUnmount = () => {
16 | if (this._interval) {
17 | clearInterval(this._interval);
18 | }
19 | };
20 |
21 | public render = () => {
22 | return this.props.children(new Date());
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/common/loading.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { Spinner } from './spinner';
5 |
6 | interface Props extends HTMLAttributes {
7 | minHeight?: string;
8 | }
9 |
10 | export const Loading: React.FC = props => {
11 | return (
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | const LoadingContainer = styled.div`
19 | ${props => `min-height: ${props.minHeight}`};
20 | position: relative;
21 | `;
22 |
23 | LoadingContainer.defaultProps = {
24 | minHeight: '200px',
25 | };
26 |
27 | const CenteredLoading = styled(Loading)`
28 | left: 50%;
29 | position: absolute;
30 | top: 50%;
31 | transform: translate(-50%, -50%);
32 | `;
33 |
34 | export const LoadingWrapper: React.FC = props => {
35 | const { ...restProps } = props;
36 |
37 | return (
38 |
39 |
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/components/common/logo.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints } from '../../themes/commons';
5 |
6 | interface Props {
7 | image: React.ReactNode;
8 | text: string;
9 | textColor?: string;
10 | onClick: (event: MouseEvent) => void;
11 | }
12 |
13 | const LogoLink = styled.a`
14 | align-items: center;
15 | cursor: pointer;
16 | display: flex;
17 | height: 33px;
18 | font-family: 'Inter var', sans-serif;
19 | text-decoration: none;
20 | `;
21 |
22 | const LogoText = styled.h1<{ textColor?: string }>`
23 | color: ${props => props.textColor};
24 | display: none;
25 | font-size: 18px;
26 | font-weight: 500;
27 | margin-left: 10px;
28 | text-decoration: none;
29 |
30 | @media (min-width: ${themeBreakPoints.xxl}) {
31 | display: block;
32 | }
33 | `;
34 |
35 | LogoText.defaultProps = {
36 | textColor: '#000',
37 | };
38 |
39 | export const Logo: React.FC = props => {
40 | const { image, text, textColor, onClick, ...restProps } = props;
41 | return (
42 |
43 | {image}
44 | {text}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/components/common/main_scrollable_wrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeDimensions } from '../../themes/commons';
5 |
6 | interface Props extends HTMLAttributes {
7 | children: React.ReactNode;
8 | }
9 |
10 | const MainScrollable = styled.div`
11 | display: flex;
12 | flex-direction: column;
13 | flex-grow: 1;
14 | margin: -${themeDimensions.mainPadding};
15 | overflow: auto;
16 | padding: ${themeDimensions.mainPadding};
17 | `;
18 |
19 | export const MainScrollableWrapper: React.FC = props => {
20 | const { children, ...restProps } = props;
21 |
22 | return {children};
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/common/pending_time.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | interface Props {
5 | estimatedTimeMs: number;
6 | now: Date;
7 | startTime: Date;
8 | }
9 |
10 | const PendingTimeWrapper = styled.span`
11 | color: ${props => props.theme.componentsTheme.textLight};
12 | `;
13 |
14 | export const PendingTime: React.FC = ({ estimatedTimeMs, now, startTime, ...restProps }) => {
15 | const estimatedSeconds = Math.round(estimatedTimeMs / 1000);
16 | const finishTime = new Date(startTime.valueOf() + estimatedTimeMs);
17 |
18 | const totalPendingSeconds = now < finishTime ? Math.round((finishTime.valueOf() - now.valueOf()) / 1000) : 0;
19 |
20 | const pendingMinutes = Math.floor(totalPendingSeconds / 60);
21 | const pendingSeconds = totalPendingSeconds % 60;
22 |
23 | const pendingMinutesStr = pendingMinutes < 10 ? `0${pendingMinutes}` : pendingMinutes.toString();
24 | const pendingSecondsStr = pendingSeconds < 10 ? `0${pendingSeconds}` : pendingSeconds.toString();
25 |
26 | return (
27 |
28 | {pendingMinutesStr}:{pendingSecondsStr} (Est. {estimatedSeconds} seconds)
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/common/show_number_with_colors.tsx:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 | import React from 'react';
3 | import styled from 'styled-components';
4 |
5 | import { padRightSplitted } from '../../util/number_utils';
6 |
7 | interface ShowNumberWithColorsProps {
8 | num: BigNumber;
9 | isHover?: boolean;
10 | }
11 |
12 | interface SpanRightProps {
13 | isHover?: boolean;
14 | }
15 |
16 | class ShowNumberWithColors extends React.Component {
17 | public render = () => {
18 | const { num, isHover } = this.props;
19 | const numSplitted = padRightSplitted(num);
20 | const SpanLeft = styled.span`
21 | color: ${props => props.theme.componentsTheme.textColorCommon};
22 | `;
23 | const SpanRight = styled.span`
24 | color: ${props =>
25 | props.isHover
26 | ? props.theme.componentsTheme.textColorCommon
27 | : props.theme.componentsTheme.numberDecimalsColor};
28 | `;
29 |
30 | return (
31 | <>
32 | {numSplitted.num}
33 | {numSplitted.diff}
34 | >
35 | );
36 | };
37 | }
38 |
39 | export { ShowNumberWithColors };
40 |
--------------------------------------------------------------------------------
/src/components/common/spinner.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled, { keyframes } from 'styled-components';
3 |
4 | import { SpinnerSize } from '../../themes/commons';
5 |
6 | import { ProcessingIcon } from './icons/processing_icon';
7 |
8 | const rotate = keyframes`
9 | from {
10 | transform: rotate(0deg);
11 | }
12 | to {
13 | transform: rotate(360deg);
14 | }
15 | `;
16 |
17 | const IconSpin = styled.div`
18 | animation: ${rotate} 1.5s linear infinite;
19 | svg {
20 | ${props => `
21 | height: ${props.size ? props.size : SpinnerSize.Small};
22 | width: ${props.size ? props.size : SpinnerSize.Small};
23 | `}
24 | }
25 | `;
26 |
27 | interface Props {
28 | size?: SpinnerSize;
29 | }
30 |
31 | const Spinner = (props: Props) => (
32 |
33 |
34 |
35 | );
36 |
37 | export { Spinner };
38 |
--------------------------------------------------------------------------------
/src/components/common/steps_modal/step_pending_time.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { PendingTime } from '../../common/pending_time';
4 | import { Interval } from '../interval';
5 |
6 | import { StepStatus } from './steps_common';
7 |
8 | interface Props {
9 | estimatedTxTimeMs: number;
10 | stepStatus: StepStatus;
11 | txStarted: number | null;
12 | }
13 |
14 | export const StepPendingTime: React.FC = props => {
15 | const { estimatedTxTimeMs, stepStatus, txStarted } = props;
16 |
17 | if (stepStatus === StepStatus.Loading && txStarted) {
18 | const startTime = new Date(txStarted);
19 | return (
20 |
21 | {now => }
22 |
23 | );
24 | }
25 |
26 | return null;
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/common/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import ReactTooltip from 'react-tooltip';
3 | import styled from 'styled-components';
4 |
5 | import { InfoIcon } from './icons/info_icon';
6 | import { InfoIconFull } from './icons/info_icon_full';
7 |
8 | export enum IconType {
9 | Line,
10 | Fill,
11 | }
12 |
13 | interface Props extends HTMLAttributes {
14 | description?: string;
15 | iconType?: IconType;
16 | }
17 |
18 | const TooltipPopup = styled.div`
19 | cursor: pointer;
20 | display: flex;
21 | justify-content: center;
22 | outline: none;
23 | position: relative;
24 |
25 | .reactTooltip {
26 | background-color: ${props => props.theme.componentsTheme.tooltipBackgroundColor};
27 | color: ${props => props.theme.componentsTheme.tooltipTextColor};
28 | max-width: 250px;
29 | opacity: 1;
30 | text-align: left;
31 |
32 | &.place-left:after {
33 | border-left-color: ${props => props.theme.componentsTheme.tooltipBackgroundColor};
34 | }
35 |
36 | &.place-right:after {
37 | border-right-color: ${props => props.theme.componentsTheme.tooltipBackgroundColor};
38 | }
39 |
40 | &.place-top:after {
41 | border-top-color: ${props => props.theme.componentsTheme.tooltipBackgroundColor};
42 | }
43 |
44 | &.place-bottom:after {
45 | border-bottom-color: ${props => props.theme.componentsTheme.tooltipBackgroundColor};
46 | }
47 |
48 | .multi-line {
49 | text-align: left;
50 | }
51 | }
52 | `;
53 |
54 | export const Tooltip: React.FC = props => {
55 | const { iconType = IconType.Line, description, ...restProps } = props;
56 | const tooltipIcon = iconType === IconType.Fill ? : ;
57 |
58 | return (
59 |
60 | {tooltipIcon}
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/src/components/common/view_all.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import styled from 'styled-components';
4 |
5 | import { ViewAllIcon } from './icons/view_all_icon';
6 |
7 | interface Props {
8 | to: string;
9 | text: string;
10 | }
11 |
12 | const ViewAllWrapper = styled(Link)`
13 | align-items: center;
14 | color: ${props => props.theme.componentsTheme.textColorCommon};
15 | display: flex;
16 | font-size: 14px;
17 | font-weight: 500;
18 | line-height: 1.2;
19 | text-decoration: none;
20 |
21 | svg {
22 | margin: 0 0 0 8px;
23 | }
24 | `;
25 |
26 | export const ViewAll: React.FC = props => {
27 | const { to, text, ...restProps } = props;
28 | return (
29 |
30 | {text}
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/components/erc20/account/wallet_connection_content.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import CopyToClipboard from 'react-copy-to-clipboard';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 |
6 | import { getEthAccount } from '../../../store/selectors';
7 | import { truncateAddress } from '../../../util/number_utils';
8 | import { StoreState } from '../../../util/types';
9 | import { WalletConnectionStatusContainer } from '../../account/wallet_connection_status';
10 | import { CardBase } from '../../common/card_base';
11 | import { DropdownTextItem } from '../../common/dropdown_text_item';
12 |
13 | interface OwnProps extends HTMLAttributes {}
14 |
15 | interface StateProps {
16 | ethAccount: string;
17 | }
18 |
19 | type Props = StateProps & OwnProps;
20 |
21 | const connectToWallet = () => {
22 | alert('connect to another wallet');
23 | };
24 |
25 | const goToURL = () => {
26 | alert('go to url');
27 | };
28 |
29 | const DropdownItems = styled(CardBase)`
30 | box-shadow: ${props => props.theme.componentsTheme.boxShadow};
31 | min-width: 240px;
32 | `;
33 |
34 | class WalletConnectionContent extends React.PureComponent {
35 | public render = () => {
36 | const { ethAccount, ...restProps } = this.props;
37 | const ethAccountText = ethAccount ? `${truncateAddress(ethAccount)}` : 'Not connected';
38 |
39 | const content = (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 |
49 | return (
50 |
56 | );
57 | };
58 | }
59 |
60 | const mapStateToProps = (state: StoreState): StateProps => {
61 | return {
62 | ethAccount: getEthAccount(state),
63 | };
64 | };
65 |
66 | const WalletConnectionContentContainer = connect(
67 | mapStateToProps,
68 | {},
69 | )(WalletConnectionContent);
70 |
71 | export { WalletConnectionContent, WalletConnectionContentContainer };
72 |
--------------------------------------------------------------------------------
/src/components/erc20/common/content_wrapper.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { themeBreakPoints, themeDimensions } from '../../../themes/commons';
4 |
5 | export const Content = styled.div`
6 | display: flex;
7 | flex-direction: column;
8 | flex-grow: 1;
9 | padding: ${themeDimensions.mainPadding};
10 |
11 | @media (min-width: ${themeBreakPoints.xl}) {
12 | flex-direction: row;
13 | height: calc(100% - ${themeDimensions.footerHeight});
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/src/components/erc20/erc20_app.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch } from 'react-router';
3 | import { ThemeProvider } from 'styled-components';
4 |
5 | import { ERC20_APP_BASE_PATH } from '../../common/constants';
6 | import { AdBlockDetector } from '../../components/common/adblock_detector';
7 | import { GeneralLayout } from '../../components/general_layout';
8 | import { getThemeByMarketplace } from '../../themes/theme_meta_data_utils';
9 | import { MARKETPLACES } from '../../util/types';
10 |
11 | import { ToolbarContentContainer } from './common/toolbar_content';
12 | import { Marketplace } from './pages/marketplace';
13 | import { MyWallet } from './pages/my_wallet';
14 |
15 | const toolbar = ;
16 |
17 | export const Erc20App = () => {
18 | const themeColor = getThemeByMarketplace(MARKETPLACES.ERC20);
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/erc20/marketplace/cancel_order_button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import styled from 'styled-components';
4 |
5 | import { cancelOrder } from '../../../store/actions';
6 | import { UIOrder } from '../../../util/types';
7 | import { CloseIcon } from '../../common/icons/close_icon';
8 |
9 | interface OwnProps {
10 | order: UIOrder;
11 | }
12 |
13 | interface DispatchProps {
14 | onCancelOrder: (order: UIOrder) => Promise;
15 | }
16 |
17 | type Props = OwnProps & DispatchProps;
18 |
19 | interface State {
20 | isLoading: boolean;
21 | }
22 |
23 | const Button = styled.button`
24 | align-items: center;
25 | background: none;
26 | border: none;
27 | display: flex;
28 | height: 17px;
29 | justify-content: flex-end;
30 | margin-left: auto;
31 | outline: 0;
32 | padding: 0;
33 | width: 25px;
34 |
35 | &:hover {
36 | cursor: pointer;
37 | }
38 |
39 | &[disabled] {
40 | cursor: default;
41 | }
42 | `;
43 |
44 | class CancelOrderButton extends React.Component {
45 | public state = {
46 | isLoading: false,
47 | };
48 |
49 | public render = () => {
50 | const { isLoading } = this.state;
51 | return (
52 |
55 | );
56 | };
57 |
58 | private readonly _cancelOrder = async () => {
59 | this.setState({ isLoading: true });
60 | try {
61 | const { order, onCancelOrder } = this.props;
62 | await onCancelOrder(order);
63 | } catch (err) {
64 | alert(`Could not cancel the specified order`);
65 | } finally {
66 | this.setState({ isLoading: false });
67 | }
68 | };
69 | }
70 |
71 | const mapDispatchToProps = {
72 | onCancelOrder: cancelOrder,
73 | };
74 |
75 | const CancelOrderButtonContainer = connect(
76 | null,
77 | mapDispatchToProps,
78 | )(CancelOrderButton);
79 |
80 | export { CancelOrderButton, CancelOrderButtonContainer };
81 |
--------------------------------------------------------------------------------
/src/components/erc20/pages/marketplace.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CheckMetamaskStateModalContainer } from '../../common/check_metamask_state_modal_container';
4 | import { ColumnNarrow } from '../../common/column_narrow';
5 | import { ColumnWide } from '../../common/column_wide';
6 | import { Content } from '../common/content_wrapper';
7 | import { BuySellContainer } from '../marketplace/buy_sell';
8 | import { OrderBookTableContainer } from '../marketplace/order_book';
9 | import { OrderHistoryContainer } from '../marketplace/order_history';
10 | import { WalletBalanceContainer } from '../marketplace/wallet_balance';
11 |
12 | class Marketplace extends React.PureComponent {
13 | public render = () => {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 | }
31 |
32 | export { Marketplace };
33 |
--------------------------------------------------------------------------------
/src/components/erc20/pages/my_wallet.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { WalletTokenBalancesContainer, WalletWethBalanceContainer } from '../../account';
5 | import { CheckMetamaskStateModalContainer } from '../../common/check_metamask_state_modal_container';
6 | import { ColumnNarrow } from '../../common/column_narrow';
7 | import { ColumnWide } from '../../common/column_wide';
8 | import { Content } from '../common/content_wrapper';
9 |
10 | const ColumnWideMyWallet = styled(ColumnWide)`
11 | margin-left: 0;
12 |
13 | &:last-child {
14 | margin-left: 0;
15 | }
16 | `;
17 |
18 | export const MyWallet = () => (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectible_details_item_list.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeDimensions } from '../../../themes/commons';
5 | import { ChevronRight } from '../../common/icons/chevron_right_icon';
6 |
7 | interface Props extends HTMLAttributes {
8 | color: string;
9 | image: string;
10 | name: string;
11 | onClick: (event: React.MouseEvent) => any;
12 | }
13 |
14 | const CollectibleAssetWrapper = styled.div`
15 | border-bottom: 1px solid ${props => props.theme.componentsTheme.cardBorderColor};
16 | cursor: pointer;
17 | display: flex;
18 | padding: 12px ${props => props.theme.modalTheme.content.padding};
19 |
20 | &:hover {
21 | background-color: rgba(0, 0, 0, 0.03);
22 | }
23 |
24 | &:last-child {
25 | border-bottom: none;
26 | }
27 | `;
28 |
29 | const ImageWrapper = styled.div<{ color: string; image: string }>`
30 | background-color: ${props => props.color || props.theme.componentsTheme.cardBackgroundColor};
31 | background-image: url('${props => props.image}');
32 | background-position: 50% 50%;
33 | background-size: contain;
34 | border-radius: ${themeDimensions.borderRadius};
35 | border: 1px solid ${props => props.theme.componentsTheme.cardBorderColor};
36 | height: 72px;
37 | margin-right: 15px;
38 | width: 72px;
39 | `;
40 |
41 | const TextContainer = styled.div`
42 | align-items: center;
43 | display: flex;
44 | flex-grow: 1;
45 | padding: 0 10px 0 0;
46 | overflow: hidden;
47 | `;
48 |
49 | const Title = styled.h3`
50 | color: ${props => props.theme.componentsTheme.cardTitleColor};
51 | font-size: 16px;
52 | font-weight: 600;
53 | line-height: 1.2;
54 | margin: 0 0 5px;
55 | overflow: hidden;
56 | padding: 0;
57 | text-overflow: ellipsis;
58 | white-space: nowrap;
59 |
60 | &:last-child {
61 | margin-bottom: 0;
62 | }
63 | `;
64 |
65 | const ChevronContainer = styled.div`
66 | align-items: center;
67 | display: flex;
68 | flex-shrink: 0;
69 | `;
70 |
71 | export const ListItem: React.FC = (props: Props) => {
72 | const { onClick, color, image, name } = props;
73 | return (
74 |
75 |
76 |
77 | {name}
78 |
79 |
80 |
81 |
82 |
83 | );
84 | };
85 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectible_details_item_tile.tsx:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 | import React, { HTMLAttributes } from 'react';
3 | import styled from 'styled-components';
4 |
5 | import { themeDimensions, themeFeatures } from '../../../themes/commons';
6 |
7 | import { PriceBadge } from './price_badge';
8 |
9 | interface Props extends HTMLAttributes {
10 | color: string;
11 | image: string;
12 | name: string;
13 | onClick: (event: React.MouseEvent) => any;
14 | price: BigNumber | null;
15 | }
16 |
17 | const CollectibleAssetWrapper = styled.div`
18 | background: ${props => props.theme.componentsTheme.cardBackgroundColor};
19 | border-radius: ${themeDimensions.borderRadius};
20 | border: 1px solid ${props => props.theme.componentsTheme.cardBorderColor};
21 | box-sizing: border-box;
22 | cursor: pointer;
23 | position: relative;
24 | transition: box-shadow 0.15s linear;
25 |
26 | &:hover {
27 | box-shadow: ${themeFeatures.boxShadow};
28 | }
29 | `;
30 |
31 | const ImageWrapper = styled.div<{ color: string; image: string }>`
32 | background-color: ${props => props.color || props.theme.componentsTheme.cardBackgroundColor};
33 | background-image: url('${props => props.image}');
34 | background-position: 50% 50%;
35 | background-size: contain;
36 | border-top-left-radius: ${themeDimensions.borderRadius};
37 | border-top-right-radius: ${themeDimensions.borderRadius};
38 | height: 272px;
39 | `;
40 |
41 | const Title = styled.h2`
42 | color: ${props => props.theme.componentsTheme.cardTitleColor};
43 | font-size: 14px;
44 | font-weight: 500;
45 | line-height: 1.2;
46 | margin: 0;
47 | overflow: hidden;
48 | padding: 10px 12px;
49 | text-overflow: ellipsis;
50 | white-space: nowrap;
51 | `;
52 |
53 | export const TileItem: React.FC = (props: Props) => {
54 | const { onClick, color, image, price, name } = props;
55 | return (
56 |
57 |
58 |
59 |
60 | {name}
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectible_details_list.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import { selectCollectible } from '../../../store/collectibles/actions';
5 | import { getCollectiblePrice } from '../../../util/collectibles';
6 | import { Collectible } from '../../../util/types';
7 |
8 | import { ListItem } from './collectible_details_item_list';
9 | import { TileItem } from './collectible_details_item_tile';
10 |
11 | interface OwnProps extends HTMLAttributes {
12 | collectible: Collectible;
13 | isListItem?: boolean;
14 | onClick: () => any;
15 | }
16 |
17 | interface DispatchProps {
18 | updateSelectedCollectible: (collectible: Collectible) => any;
19 | }
20 |
21 | type Props = DispatchProps & OwnProps;
22 |
23 | export const CollectibleOnList: React.FC = (props: Props) => {
24 | const { collectible, onClick, isListItem } = props;
25 | const { color, image, name } = collectible;
26 | const price = getCollectiblePrice(collectible);
27 |
28 | const handleAssetClick: React.EventHandler = (event: React.MouseEvent) => {
29 | event.preventDefault();
30 | onClick();
31 | try {
32 | props.updateSelectedCollectible(collectible);
33 | } catch (err) {
34 | window.alert(`Could not sell the specified order`);
35 | }
36 | };
37 |
38 | return isListItem ? (
39 |
40 | ) : (
41 |
42 | );
43 | };
44 |
45 | const mapDispatchToProps = (dispatch: any): DispatchProps => {
46 | return {
47 | updateSelectedCollectible: (collectible: Collectible) => dispatch(selectCollectible(collectible)),
48 | };
49 | };
50 |
51 | export const CollectibleOnListContainer = connect(
52 | null,
53 | mapDispatchToProps,
54 | )(CollectibleOnList);
55 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectibles_dropdown_button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { ChevronDownIcon } from '../../common/icons/chevron_down_icon';
5 |
6 | interface Props {
7 | text: string;
8 | extraIcon: any;
9 | }
10 |
11 | const DropdownButtonContainer = styled.div`
12 | align-items: center;
13 | display: flex;
14 | justify-content: space-between;
15 | user-select: none;
16 | `;
17 |
18 | const Text = styled.span`
19 | margin: 0 10px;
20 | `;
21 |
22 | export class DropdownButton extends React.Component {
23 | public render = () => {
24 | const { text, extraIcon } = this.props;
25 |
26 | return (
27 |
28 | {extraIcon}
29 | {text}
30 |
31 |
32 | );
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectibles_dropdown_container.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { CardBase } from '../../common/card_base';
5 |
6 | interface Props {
7 | children: React.ReactNode;
8 | }
9 |
10 | const DropdownItemsContainer = styled(CardBase)`
11 | box-shadow: ${props => props.theme.componentsTheme.boxShadow};
12 | min-width: 240px;
13 | `;
14 |
15 | export class DropdownContainer extends React.Component {
16 | public render = () => {
17 | const { children } = this.props;
18 |
19 | return {children};
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectibles_list_filter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { CollectibleFilterType } from '../../../util/filterable_collectibles';
5 | import { Dropdown, DropdownPositions } from '../../common/dropdown';
6 | import { DropdownTextItemWrapper } from '../../common/dropdown_text_item';
7 | import { FilterIcon } from '../../common/icons/filter_icon';
8 |
9 | import { DropdownButton } from './collectibles_dropdown_button';
10 | import { DropdownContainer } from './collectibles_dropdown_container';
11 |
12 | interface Props {
13 | currentValue: CollectibleFilterType;
14 | onChange: (evt: React.ChangeEvent) => void;
15 | }
16 |
17 | const options: { [key: string]: string } = {
18 | [CollectibleFilterType.ShowAll]: 'Show all',
19 | [CollectibleFilterType.FixedPrice]: 'Fixed Price',
20 | [CollectibleFilterType.DecliningAuction]: 'Declining Auction',
21 | };
22 |
23 | const DropdownItemFilter = styled(DropdownTextItemWrapper)`
24 | ${props => (props.active ? 'cursor: default;' : '')}
25 | position: relative;
26 |
27 | input[type='radio'] {
28 | cursor: pointer;
29 | height: 100%;
30 | left: 0;
31 | opacity: 0;
32 | position: absolute;
33 | top: 0;
34 | width: 100%;
35 | z-index: 5;
36 |
37 | &:checked {
38 | cursor: default;
39 | pointer-events: none;
40 | }
41 |
42 | &:checked + span {
43 | font-weight: 500;
44 | }
45 | }
46 | `;
47 |
48 | const Text = styled.span`
49 | position: relative;
50 | z-index: 1;
51 | `;
52 |
53 | export const CollectiblesListFilter = (props: Props) => {
54 | const { currentValue, onChange, ...restProps } = props;
55 | const filterTypes = Object.keys(options) as CollectibleFilterType[];
56 | const header = } />;
57 |
58 | const body = (
59 |
60 | {filterTypes.map(filterType => (
61 |
62 |
63 | {options[filterType]}
64 |
65 | ))}
66 |
67 | );
68 |
69 | return ;
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectibles_list_sort.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { CollectibleSortType } from '../../../util/sortable_collectibles';
5 | import { Dropdown, DropdownPositions } from '../../common/dropdown';
6 | import { DropdownTextItemWrapper } from '../../common/dropdown_text_item';
7 | import { SortIcon } from '../../common/icons/sort_icon';
8 |
9 | import { DropdownButton } from './collectibles_dropdown_button';
10 | import { DropdownContainer } from './collectibles_dropdown_container';
11 |
12 | interface Props {
13 | currentValue: CollectibleSortType;
14 | onChange: (evt: React.ChangeEvent) => void;
15 | }
16 |
17 | const options: { [key: string]: string } = {
18 | [CollectibleSortType.NewestAdded]: 'Newest Added',
19 | [CollectibleSortType.PriceLowToHigh]: 'Price: low to high',
20 | [CollectibleSortType.PriceHighToLow]: 'Price: high to low',
21 | };
22 |
23 | const DropdownItemFilter = styled(DropdownTextItemWrapper)`
24 | ${props => (props.active ? 'cursor: default;' : '')}
25 | position: relative;
26 |
27 | input[type='radio'] {
28 | cursor: pointer;
29 | height: 100%;
30 | left: 0;
31 | opacity: 0;
32 | position: absolute;
33 | top: 0;
34 | width: 100%;
35 | z-index: 5;
36 |
37 | &:checked {
38 | cursor: default;
39 | pointer-events: none;
40 | }
41 |
42 | &:checked + span {
43 | font-weight: 500;
44 | }
45 | }
46 | `;
47 |
48 | const Text = styled.span`
49 | position: relative;
50 | z-index: 1;
51 | `;
52 |
53 | export const CollectiblesListSort = (props: Props) => {
54 | const { currentValue, onChange, ...restProps } = props;
55 | const sortTypes = Object.keys(options) as CollectibleSortType[];
56 | const header = } />;
57 |
58 | const body = (
59 |
60 | {sortTypes.map(sortType => (
61 |
62 |
63 | {options[sortType]}
64 |
65 | ))}
66 |
67 | );
68 |
69 | return ;
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/collectibles_search.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { Theme } from '../../../themes/commons';
4 | import { InputSearch } from '../common/input_search';
5 | import { SearchModalContainer } from '../modals/search-modal';
6 |
7 | interface Props {
8 | theme: Theme;
9 | }
10 |
11 | export const CollectiblesSearch: React.FC = props => {
12 | const [isModalOpen, setIsModalOpen] = useState(false);
13 |
14 | const showModal = () => setIsModalOpen(true);
15 | const hideModal = () => setIsModalOpen(false);
16 |
17 | return (
18 | <>
19 |
20 |
21 | >
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/owner_badge.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Badge = styled.div`
5 | align-items: center;
6 | background: ${props => props.theme.componentsTheme.cardBackgroundColor};
7 | border-radius: 16px;
8 | box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.04);
9 | display: flex;
10 | height: 31px;
11 | justify-content: center;
12 | min-width: 84px;
13 | padding: 8px 12px;
14 | position: absolute;
15 | left: 10px;
16 | top: 10px;
17 | `;
18 |
19 | const BadgeValue = styled.span`
20 | color: ${props => props.theme.componentsTheme.cardTitleOwnerColor};
21 | font-size: 14px;
22 | font-weight: 400;
23 | line-height: 14px;
24 | `;
25 |
26 | export const OwnerBadge = () => {
27 | return (
28 |
29 | You Own
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/erc721/collectibles/price_badge.tsx:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 | import React from 'react';
3 | import styled from 'styled-components';
4 |
5 | import { ETH_DECIMALS } from '../../../common/constants';
6 | import { tokenAmountInUnits } from '../../../util/tokens';
7 |
8 | interface Props {
9 | price: BigNumber | null;
10 | }
11 |
12 | const Badge = styled.div`
13 | align-items: center;
14 | background: ${props => props.theme.componentsTheme.cardBackgroundColor};
15 | border-radius: 16px;
16 | box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.04);
17 | display: flex;
18 | height: 31px;
19 | justify-content: center;
20 | padding: 8px 12px;
21 | position: absolute;
22 | right: 10px;
23 | top: 10px;
24 | `;
25 |
26 | const BadgeValue = styled.span`
27 | color: ${props => props.theme.componentsTheme.cardTitleColor};
28 | font-feature-settings: 'tnum' on, 'onum' on;
29 | font-size: 14px;
30 | font-weight: 400;
31 | line-height: 14px;
32 | margin-right: 6px;
33 | `;
34 |
35 | const BadgeAsset = styled.span`
36 | color: ${props => props.theme.componentsTheme.cardTitleColor};
37 | font-feature-settings: 'tnum' on, 'onum' on;
38 | font-size: 10px;
39 | font-weight: 400;
40 | line-height: 10px;
41 | `;
42 |
43 | export const PriceBadge = (props: Props) => {
44 | const { price } = props;
45 | if (price === null) {
46 | return null;
47 | }
48 | return (
49 |
50 | {tokenAmountInUnits(price, ETH_DECIMALS)}
51 | ETH
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/src/components/erc721/common/content_wrapper.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { themeBreakPoints, themeDimensions } from '../../../themes/commons';
4 |
5 | export const Content = styled.div`
6 | display: flex;
7 | flex-direction: column;
8 | flex-grow: 1;
9 | padding: ${themeDimensions.mainPadding};
10 |
11 | @media (min-width: ${themeBreakPoints.xl}) {
12 | flex-direction: row;
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/src/components/erc721/common/input_search.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled, { css } from 'styled-components';
3 |
4 | import { themeBreakPoints, themeDimensions } from '../../../themes/commons';
5 |
6 | const noFocusOutline = css`
7 | :focus {
8 | outline: none;
9 | }
10 | `;
11 |
12 | const SearchInput = styled.input`
13 | background-color: #f9fafc;
14 | background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAxNCAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjQiIHN0cm9rZT0iI0RFREVERSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xMi41IDExLjVMOCA3IiBzdHJva2U9IiNERURFREUiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K');
15 | background-position: 12px 50%;
16 | background-repeat: no-repeat;
17 | border-radius: ${themeDimensions.borderRadius};
18 | border: 1px solid ${props => props.theme.componentsTheme.cardBorderColor};
19 | box-sizing: border-box;
20 | color: #666;
21 | font-size: 14px;
22 | height: 32px;
23 | margin: 0 auto 0 10px;
24 | padding-left: 35px;
25 | width: 140px;
26 |
27 | @media (min-width: ${themeBreakPoints.md}) {
28 | margin-left: 20px;
29 | width: 250px;
30 | }
31 |
32 | @media (min-width: ${themeBreakPoints.xxl}) {
33 | margin: 0;
34 | width: 466px;
35 | }
36 |
37 | ::placeholder {
38 | color: #bfbfbf;
39 | font-size: 14px;
40 | text-align: center;
41 | }
42 |
43 | ${props => (props.focusOutline ? '' : noFocusOutline)}
44 | `;
45 |
46 | SearchInput.defaultProps = {
47 | focusOutline: true,
48 | };
49 |
50 | interface Props {
51 | placeholder: string;
52 | onChange?: (event: React.ChangeEvent) => any;
53 | onClick?: (event: React.MouseEvent) => any;
54 | autoFocus?: boolean;
55 | readOnly?: boolean;
56 | focusOutline?: boolean;
57 | }
58 |
59 | export const InputSearch = (props: Props) => {
60 | return ;
61 | };
62 |
--------------------------------------------------------------------------------
/src/components/erc721/erc721_app.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch } from 'react-router';
3 | import styled, { ThemeProvider } from 'styled-components';
4 |
5 | import { ERC721_APP_BASE_PATH } from '../../common/constants';
6 | import { getThemeByMarketplace } from '../../themes/theme_meta_data_utils';
7 | import { MARKETPLACES } from '../../util/types';
8 | import { AdBlockDetector } from '../common/adblock_detector';
9 | import { CheckMetamaskStateModalContainer } from '../common/check_metamask_state_modal_container';
10 | import { GeneralLayout } from '../general_layout';
11 |
12 | import { CollectibleSellModal } from './collectibles/collectible_sell_modal';
13 | import { ToolbarContentContainer } from './common/toolbar_content';
14 | import { AllCollectibles } from './pages/all_collectibles';
15 | import { IndividualCollectible } from './pages/individual_collectible';
16 | import { ListCollectibles } from './pages/list_collectibles';
17 | import { MyCollectibles } from './pages/my_collectibles';
18 |
19 | const toolbar = ;
20 |
21 | const GeneralLayoutERC721 = styled(GeneralLayout)`
22 | background-color: ${props => props.theme.componentsTheme.backgroundERC721};
23 | `;
24 |
25 | export const Erc721App = () => {
26 | const themeColor = getThemeByMarketplace(MARKETPLACES.ERC721);
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
41 |
42 | {({ match }) => match && }
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/erc721/marketplace/marketplace_common.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TitleText = styled.h3`
4 | font-size: 14px;
5 | line-height: 17px;
6 | font-weight: 500;
7 | `;
8 |
--------------------------------------------------------------------------------
/src/components/erc721/marketplace/sell_collectibles_button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints } from '../../../themes/commons';
5 | import { ButtonVariant } from '../../../util/types';
6 | import { Button } from '../../common/button';
7 | import { CollectibleListModal } from '../collectibles/collectible_list_modal';
8 |
9 | const ButtonStyled = styled(Button)`
10 | min-width: 195px;
11 | @media (min-width: ${themeBreakPoints.md}) {
12 | margin-left: auto;
13 | }
14 | `;
15 |
16 | export class SellCollectiblesButton extends React.Component {
17 | public state = {
18 | isModalOpen: false,
19 | };
20 | public render = () => {
21 | return (
22 | <>
23 |
24 |
25 | Sell collectibles
26 |
27 | >
28 | );
29 | };
30 |
31 | private readonly _handleModalToggle = () => {
32 | this.setState({
33 | isModalOpen: !this.state.isModalOpen,
34 | });
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/erc721/pages/all_collectibles.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { COLLECTIBLE_DESCRIPTION, COLLECTIBLE_NAME } from '../../../common/constants';
4 | import { AllCollectiblesContainer } from '../collectibles/collectibles_all';
5 | import { Content } from '../common/content_wrapper';
6 |
7 | export const AllCollectibles = () => (
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/src/components/erc721/pages/individual_collectible.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints } from '../../../themes/commons';
5 | import { Content } from '../common/content_wrapper';
6 | import { CollectibleBuySellContainer } from '../marketplace/collectible_buy_sell';
7 | import { CollectibleDescriptionContainer } from '../marketplace/collectible_description';
8 |
9 | const IndividualCollectibleWrapper = styled.div`
10 | align-items: center;
11 | display: flex;
12 | flex-direction: column;
13 | flex-grow: 1;
14 | margin: 0 auto;
15 | max-width: ${themeBreakPoints.xxl};
16 | width: 100%;
17 |
18 | @media (min-width: ${themeBreakPoints.md}) {
19 | align-items: flex-start;
20 | flex-direction: row;
21 | justify-content: center;
22 | }
23 | `;
24 |
25 | const CollectibleBuySell = styled(CollectibleBuySellContainer)`
26 | flex-grow: 0;
27 | flex-shrink: 0;
28 |
29 | @media (min-width: ${themeBreakPoints.md}) {
30 | margin-right: 12px;
31 | }
32 | `;
33 |
34 | const CollectibleDescription = styled(CollectibleDescriptionContainer)`
35 | display: flex;
36 | flex-direction: column;
37 | max-width: 586px;
38 | min-width: 0;
39 | width: 100%;
40 | `;
41 |
42 | interface OwnProps {
43 | collectibleId: string;
44 | }
45 |
46 | type Props = OwnProps;
47 |
48 | export const IndividualCollectible = (props: Props) => {
49 | const { collectibleId } = props;
50 | if (!collectibleId) {
51 | return null;
52 | }
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/src/components/erc721/pages/list_collectibles.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { COLLECTIBLE_NAME } from '../../../common/constants';
4 | import { AllCollectiblesListContainer } from '../collectibles/collectibles_list';
5 | import { Content } from '../common/content_wrapper';
6 |
7 | export const ListCollectibles = () => (
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/src/components/erc721/pages/my_collectibles.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { MyCollectiblesListContainer } from '../collectibles/collectibles_list';
4 | import { Content } from '../common/content_wrapper';
5 |
6 | export const MyCollectibles = () => (
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/general_layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { themeBreakPoints, themeDimensions } from '../themes/commons';
5 |
6 | import { Footer } from './common/footer';
7 | import { StepsModalContainer } from './common/steps_modal/steps_modal';
8 |
9 | const General = styled.div`
10 | background: ${props => props.theme.componentsTheme.background};
11 | display: flex;
12 | flex-direction: column;
13 | min-height: 100%;
14 |
15 | @media (min-width: ${themeBreakPoints.xl}) {
16 | height: 100%;
17 | }
18 | `;
19 |
20 | const ContentScroll = styled.div`
21 | display: flex;
22 | flex-direction: column;
23 | flex-grow: 1;
24 |
25 | @media (min-width: ${themeBreakPoints.xl}) {
26 | height: calc(100% - ${themeDimensions.toolbarHeight});
27 | overflow: auto;
28 | }
29 | `;
30 |
31 | interface OwnProps {
32 | children: React.ReactNode;
33 | toolbar: React.ReactNode;
34 | }
35 |
36 | type Props = OwnProps;
37 |
38 | export const GeneralLayout = (props: Props) => {
39 | const { children, toolbar, ...restProps } = props;
40 | return (
41 |
42 | {toolbar}
43 |
44 | {children}
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/src/exceptions/common.ts:
--------------------------------------------------------------------------------
1 | export const INSUFFICIENT_MAKER_BALANCE_ERR = 'INSUFFICIENT MAKER BALANCE';
2 | export const SIGNATURE_ERR = 'User denied message signature';
3 | export const USER_DENIED_TRANSACTION_SIGNATURE_ERR = 'User denied transaction signature';
4 | export const INSUFFICIENT_ORDERS_TO_FILL_AMOUNT_ERR = 'There are not enough orders to fill this amount';
5 | export const RELAYER_ERR = 'There was an error with the relayer';
6 | export const INSUFFICIENT_FEE_BALANCE = 'INSUFFICIENT MAKER FEE BALANCE';
7 | export const INSUFFICIENT_FEE_BALANCE_MSG = `You don't have enough 0x to pay fees`;
8 | export const INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT';
9 | export const UNEXPECTED_ERROR = 'An unexpected error happened';
10 |
--------------------------------------------------------------------------------
/src/exceptions/component_unmounted_exception.ts:
--------------------------------------------------------------------------------
1 | export class ComponentUnmountedException extends Error {
2 | constructor(componentName: string) {
3 | super(`${componentName} unmounted`);
4 | // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
5 | Object.setPrototypeOf(this, new.target.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/exceptions/convert_balance_must_not_be_equal_exception.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | export class ConvertBalanceMustNotBeEqualException extends Error {
4 | constructor(currentEthBalance: BigNumber, newEthBalance: BigNumber) {
5 | super(
6 | `Convert ETH/WETH values must not be equal, received: ${currentEthBalance.toString()} and ${newEthBalance.toString()}`,
7 | );
8 | // Set the prototype explicitly.
9 | Object.setPrototypeOf(this, ConvertBalanceMustNotBeEqualException.prototype);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/exceptions/insufficient_eth_deposit_balance_exception.ts:
--------------------------------------------------------------------------------
1 | export class InsufficientEthDepositBalanceException extends Error {
2 | constructor(ethBalance: string, ethAmountNeeded: string) {
3 | super(`You have ${ethBalance} ETH but you need ${ethAmountNeeded} ETH to make this operation`);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/exceptions/insufficient_fee_balance_exception.ts:
--------------------------------------------------------------------------------
1 | import { INSUFFICIENT_FEE_BALANCE_MSG } from './common';
2 |
3 | export class InsufficientFeeBalanceException extends Error {
4 | constructor() {
5 | super(INSUFFICIENT_FEE_BALANCE_MSG);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/exceptions/insufficient_orders_amount_exception.ts:
--------------------------------------------------------------------------------
1 | import { INSUFFICIENT_ORDERS_TO_FILL_AMOUNT_ERR } from './common';
2 |
3 | export class InsufficientOrdersAmountException extends Error {
4 | constructor() {
5 | super(INSUFFICIENT_ORDERS_TO_FILL_AMOUNT_ERR);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/exceptions/insufficient_token_balance_exception.ts:
--------------------------------------------------------------------------------
1 | export class InsufficientTokenBalanceException extends Error {
2 | constructor(quoteSymbol: string) {
3 | super(`You don't have enough ${quoteSymbol.toUpperCase()}...`);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/exceptions/relayer_exception.ts:
--------------------------------------------------------------------------------
1 | import { getErrorResponseFrom0xConnectErrorMessage } from '../util/error_messages';
2 |
3 | import { RELAYER_ERR } from './common';
4 |
5 | export class RelayerException extends Error {
6 | constructor(m: string) {
7 | // The error object comes from the relayer as a string, we convert it to JSON before displaying it
8 | let errorMsg = m;
9 | const errorObject = getErrorResponseFrom0xConnectErrorMessage(m);
10 | if (errorObject) {
11 | // Once it's converted, we extract the error msg to display
12 | const reasonUnformated = errorObject.validationErrors[0].reason;
13 | errorMsg = reasonUnformated ? reasonUnformated.split('_').join(' ') : RELAYER_ERR;
14 | }
15 | super(errorMsg);
16 | // Set the prototype explicitly.
17 | Object.setPrototypeOf(this, RelayerException.prototype);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/exceptions/signature_failed_exception.ts:
--------------------------------------------------------------------------------
1 | export class SignatureFailedException extends Error {
2 | public stackError: string;
3 | constructor(message: string) {
4 | super('User denied message signature.');
5 | // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
6 | Object.setPrototypeOf(this, new.target.prototype);
7 | this.stackError = message;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/exceptions/signed_order_exception.ts:
--------------------------------------------------------------------------------
1 | export class SignedOrderException extends Error {
2 | constructor(m: string) {
3 | super(m);
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, SignedOrderException.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/exceptions/user_denied_transaction_exception.ts:
--------------------------------------------------------------------------------
1 | import { USER_DENIED_TRANSACTION_SIGNATURE_ERR } from './common';
2 |
3 | export class UserDeniedTransactionSignatureException extends Error {
4 | constructor(m: string = USER_DENIED_TRANSACTION_SIGNATURE_ERR) {
5 | super(m);
6 | // Set the prototype explicitly.
7 | Object.setPrototypeOf(this, UserDeniedTransactionSignatureException.prototype);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | ethereum: any;
3 | web3: any;
4 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;
5 | }
6 |
7 | declare module 'react-copy-to-clipboard';
8 | declare module 'react-tooltip';
9 | declare module '*.json' {
10 | const value: any;
11 | export default value;
12 | }
13 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://rsms.me/inter/inter.css');
2 | @import url('https://rsms.me/inter/inter-ui.css');
3 |
4 | html {
5 | font-family: 'Inter', sans-serif;
6 | }
7 |
8 | @supports (font-variation-settings: normal) {
9 | html {
10 | font-family: 'Inter var', sans-serif;
11 | }
12 | }
13 |
14 | body,
15 | html,
16 | #root {
17 | height: 100vh;
18 | }
19 |
20 | code {
21 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
22 | }
23 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { ConnectedRouter } from 'connected-react-router';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import ReactModal from 'react-modal';
5 | import { Provider } from 'react-redux';
6 | import { Redirect, Route, Switch } from 'react-router';
7 | import 'sanitize.css';
8 |
9 | import { DEFAULT_BASE_PATH, ERC20_APP_BASE_PATH, ERC721_APP_BASE_PATH, LOGGER_ID } from './common/constants';
10 | import { AppContainer } from './components/app';
11 | import { Erc20App } from './components/erc20/erc20_app';
12 | import { Erc721App } from './components/erc721/erc721_app';
13 | import './index.css';
14 | import * as serviceWorker from './serviceWorker';
15 | import { history, store } from './store';
16 |
17 | ReactModal.setAppElement('#root');
18 |
19 | if (['development', 'production'].includes(process.env.NODE_ENV) && !window.localStorage.debug) {
20 | // Log only the app constant id to the console
21 | window.localStorage.debug = `${LOGGER_ID}*`;
22 | }
23 | const RedirectToHome = () => ;
24 |
25 | const Web3WrappedApp = (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 |
39 | ReactDOM.render(Web3WrappedApp, document.getElementById('root'));
40 |
41 | // If you want your app to work offline and load faster, you can change
42 | // unregister() to register() below. Note this comes with some pitfalls.
43 | // Learn more about service workers: http://bit.ly/CRA-PWA
44 | serviceWorker.unregister();
45 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/services/collectibles_metadata_sources/index.ts:
--------------------------------------------------------------------------------
1 | import { COLLECTIBLES_SOURCE } from '../../common/constants';
2 | import { CollectibleMetadataSource } from '../../util/types';
3 |
4 | import { Mocked } from './mocked';
5 | import { Opensea } from './opensea';
6 |
7 | const sources: { [key: string]: CollectibleMetadataSource } = {
8 | opensea: new Opensea({ rps: 5 }),
9 | mocked: new Mocked(),
10 | };
11 |
12 | export const getConfiguredSource = () => {
13 | return sources[COLLECTIBLES_SOURCE.toLowerCase()];
14 | };
15 |
--------------------------------------------------------------------------------
/src/services/collectibles_metadata_sources/opensea.ts:
--------------------------------------------------------------------------------
1 | import { RateLimit } from 'async-sema';
2 |
3 | import { COLLECTIBLE_ADDRESS, NETWORK_ID, OPENSEA_API_KEY } from '../../common/constants';
4 | import { Collectible, CollectibleMetadataSource } from '../../util/types';
5 |
6 | export class Opensea implements CollectibleMetadataSource {
7 | private readonly _rateLimit: () => Promise;
8 |
9 | private readonly _endpointsUrls: { [key: number]: string } = {
10 | 1: 'https://api.opensea.io/api/v1',
11 | 4: 'https://rinkeby-api.opensea.io/api/v1',
12 | };
13 |
14 | public static getAssetsAsCollectible(assets: any[]): Collectible[] {
15 | return assets.map((asset: any) => {
16 | return Opensea.getAssetAsCollectible(asset);
17 | });
18 | }
19 |
20 | public static getAssetAsCollectible(asset: any): Collectible {
21 | return {
22 | tokenId: asset.token_id,
23 | name: asset.name || `${asset.asset_contract.name} - #${asset.token_id}`,
24 | color: asset.background_color ? `#${asset.background_color}` : '',
25 | image: asset.image_url,
26 | currentOwner: asset.owner.address,
27 | assetUrl: asset.external_link,
28 | description: asset.name,
29 | order: null,
30 | };
31 | }
32 |
33 | constructor(options: { rps: number }) {
34 | this._rateLimit = RateLimit(options.rps); // requests per second
35 | }
36 |
37 | public async fetchAllUserCollectiblesAsync(userAddress: string): Promise {
38 | const metadataSourceUrl = this._endpointsUrls[NETWORK_ID];
39 | const contractAddress = COLLECTIBLE_ADDRESS;
40 | const url = `${metadataSourceUrl}/assets?asset_contract_address=${contractAddress}&owner=${userAddress}`;
41 | const assetsResponse = await this._fetch(url);
42 | const assetsResponseJson = await assetsResponse.json();
43 | return Opensea.getAssetsAsCollectible(assetsResponseJson.assets);
44 | }
45 |
46 | public async fetchCollectiblesAsync(tokenIds: string[]): Promise {
47 | const metadataSourceUrl = this._endpointsUrls[NETWORK_ID];
48 | const contractAddress = COLLECTIBLE_ADDRESS;
49 | const tokenIdsQueryParam = tokenIds.map((id: string) => `token_ids=${id}`).join('&');
50 | const url = `${metadataSourceUrl}/assets?asset_contract_address=${contractAddress}&${tokenIdsQueryParam}`;
51 | const assetsResponse = await this._fetch(url);
52 | const assetsResponseJson = await assetsResponse.json();
53 | return Opensea.getAssetsAsCollectible(assetsResponseJson.assets);
54 | }
55 |
56 | private readonly _fetch = async (url: string) => {
57 | await this._rateLimit();
58 | return fetch(url, {
59 | headers: { 'X-API-KEY': OPENSEA_API_KEY || '' } as any,
60 | });
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/src/services/contract_wrappers.ts:
--------------------------------------------------------------------------------
1 | import { ContractWrappers } from '@0x/contract-wrappers';
2 |
3 | import { CHAIN_ID } from '../common/constants';
4 |
5 | import { getWeb3Wrapper } from './web3_wrapper';
6 |
7 | let contractWrappers: ContractWrappers;
8 |
9 | export const getContractWrappers = async () => {
10 | if (!contractWrappers) {
11 | const web3Wrapper = await getWeb3Wrapper();
12 | contractWrappers = new ContractWrappers(web3Wrapper.getProvider(), { chainId: CHAIN_ID });
13 | }
14 |
15 | return contractWrappers;
16 | };
17 |
--------------------------------------------------------------------------------
/src/services/exchange.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DecodedLogEvent,
3 | ExchangeContract,
4 | ExchangeEvents,
5 | ExchangeFillEventArgs,
6 | LogWithDecodedArgs,
7 | } from '@0x/contract-wrappers';
8 |
9 | interface SubscribeToFillEventsParams {
10 | exchange: ExchangeContract;
11 | fromBlock: number;
12 | toBlock: number;
13 | ethAccount: string;
14 | fillEventCallback: (log: LogWithDecodedArgs) => any;
15 | pastFillEventsCallback: (log: Array>) => any;
16 | }
17 |
18 | export const subscribeToFillEvents = ({
19 | exchange,
20 | fromBlock,
21 | toBlock,
22 | ethAccount,
23 | fillEventCallback,
24 | pastFillEventsCallback,
25 | }: SubscribeToFillEventsParams): string => {
26 | const subscription = exchange.subscribe(
27 | ExchangeEvents.Fill,
28 | { makerAddress: ethAccount },
29 | (err: Error | null, logEvent?: DecodedLogEvent) => {
30 | if (err || !logEvent) {
31 | // tslint:disable-next-line:no-console
32 | console.error('There was a problem with the ExchangeFill event', err, logEvent);
33 | return;
34 | }
35 | fillEventCallback(logEvent.log);
36 | },
37 | );
38 |
39 | exchange
40 | .getLogsAsync(
41 | ExchangeEvents.Fill,
42 | {
43 | fromBlock,
44 | toBlock,
45 | },
46 | {
47 | makerAddress: ethAccount,
48 | },
49 | )
50 | .then(pastFillEventsCallback);
51 |
52 | return subscription;
53 | };
54 |
--------------------------------------------------------------------------------
/src/services/gas_price_estimation.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { DEFAULT_ESTIMATED_TRANSACTION_TIME_MS, DEFAULT_GAS_PRICE, GWEI_IN_WEI } from '../common/constants';
4 | import { getLogger } from '../util/logger';
5 | import { GasInfo } from '../util/types';
6 |
7 | interface EthGasStationResult {
8 | average: number;
9 | fastestWait: number;
10 | fastWait: number;
11 | fast: number;
12 | safeLowWait: number;
13 | blockNum: number;
14 | avgWait: number;
15 | block_time: number;
16 | speed: number;
17 | fastest: number;
18 | safeLow: number;
19 | }
20 |
21 | const logger = getLogger('gas_price_estimation');
22 |
23 | const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info';
24 |
25 | export const getGasEstimationInfoAsync = async (): Promise => {
26 | let fetchedAmount: GasInfo | undefined;
27 |
28 | try {
29 | fetchedAmount = await fetchFastAmountInWeiAsync();
30 | } catch (e) {
31 | fetchedAmount = undefined;
32 | }
33 |
34 | const info = fetchedAmount || {
35 | gasPriceInWei: DEFAULT_GAS_PRICE,
36 | estimatedTimeMs: DEFAULT_ESTIMATED_TRANSACTION_TIME_MS,
37 | };
38 | logger.info(info);
39 | return info;
40 | };
41 |
42 | const fetchFastAmountInWeiAsync = async (): Promise => {
43 | const res = await fetch(`${ETH_GAS_STATION_API_BASE_URL}/json/ethgasAPI.json`);
44 | const gasInfo = (await res.json()) as EthGasStationResult;
45 | // Eth Gas Station result is gwei * 10
46 | const gasPriceInGwei = new BigNumber(gasInfo.fast / 10);
47 | // Time is in minutes
48 | const estimatedTimeMs = gasInfo.fastWait * 60 * 1000; // Minutes to MS
49 | return { gasPriceInWei: gasPriceInGwei.multipliedBy(GWEI_IN_WEI), estimatedTimeMs };
50 | };
51 |
--------------------------------------------------------------------------------
/src/services/markets.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | const ETH_MARKET_PRICE_API_ENDPOINT = 'https://api.coinmarketcap.com/v1/ticker/ethereum/';
4 |
5 | export const getMarketPriceEther = async (): Promise => {
6 | const promisePriceEtherResolved = await fetch(ETH_MARKET_PRICE_API_ENDPOINT);
7 | if (promisePriceEtherResolved.status === 200) {
8 | const data = await promisePriceEtherResolved.json();
9 | if (data && data.length) {
10 | const item = data[0];
11 | const priceTokenUSD = new BigNumber(item.price_usd);
12 | return priceTokenUSD;
13 | }
14 | }
15 |
16 | return Promise.reject('Could not get ETH price');
17 | };
18 |
--------------------------------------------------------------------------------
/src/services/tokens.ts:
--------------------------------------------------------------------------------
1 | import { assetDataUtils } from '@0x/order-utils';
2 | import { BigNumber } from '@0x/utils';
3 |
4 | import { Token, TokenBalance } from '../util/types';
5 |
6 | import { getContractWrappers } from './contract_wrappers';
7 |
8 | export const tokensToTokenBalances = async (tokens: Token[], address: string): Promise => {
9 | const contractWrappers = await getContractWrappers();
10 | const assetDatas = tokens.map(t => assetDataUtils.encodeERC20AssetData(t.address));
11 | const [balances, allowances] = await contractWrappers.devUtils
12 | .getBatchBalancesAndAssetProxyAllowances(address, assetDatas)
13 | .callAsync();
14 | const tokenBalances = balances.map((_b: any, i: any) => {
15 | return {
16 | token: tokens[i],
17 | balance: balances[i],
18 | isUnlocked: allowances[i].isGreaterThan(0),
19 | };
20 | });
21 | return tokenBalances;
22 | };
23 | export const tokenToTokenBalance = async (token: Token, address: string): Promise => {
24 | const [tokenBalance] = await tokensToTokenBalances([token], address);
25 | return tokenBalance;
26 | };
27 |
28 | export const getTokenBalance = async (token: Token, address: string): Promise => {
29 | const balance = await tokenToTokenBalance(token, address);
30 | return balance.balance;
31 | };
32 |
--------------------------------------------------------------------------------
/src/services/web3_wrapper.ts:
--------------------------------------------------------------------------------
1 | import { Web3Wrapper } from '@0x/web3-wrapper';
2 |
3 | import { sleep } from '../util/sleep';
4 |
5 | let web3Wrapper: Web3Wrapper | null = null;
6 |
7 | export const isMetamaskInstalled = (): boolean => {
8 | const { ethereum, web3 } = window;
9 | return ethereum || web3;
10 | };
11 |
12 | export const initializeWeb3Wrapper = async (): Promise => {
13 | const { ethereum, web3, location } = window;
14 |
15 | if (web3Wrapper) {
16 | return web3Wrapper;
17 | }
18 |
19 | if (ethereum) {
20 | try {
21 | web3Wrapper = new Web3Wrapper(ethereum);
22 | // Request account access if needed
23 | await ethereum.enable();
24 |
25 | // Subscriptions register
26 | ethereum.on('accountsChanged', async (accounts: []) => {
27 | // Reload to avoid MetaMask bug: "MetaMask - RPC Error: Internal JSON-RPC"
28 | location.reload();
29 | });
30 | ethereum.on('networkChanged', async (network: number) => {
31 | location.reload();
32 | });
33 |
34 | return web3Wrapper;
35 | } catch (error) {
36 | // The user denied account access
37 | return null;
38 | }
39 | } else if (web3) {
40 | web3Wrapper = new Web3Wrapper(web3.currentProvider);
41 | return web3Wrapper;
42 | } else {
43 | // The user does not have metamask installed
44 | return null;
45 | }
46 | };
47 |
48 | export const getWeb3Wrapper = async (): Promise => {
49 | while (!web3Wrapper) {
50 | // if web3Wrapper is not set yet, wait and retry
51 | await sleep(100);
52 | }
53 |
54 | return web3Wrapper;
55 | };
56 |
--------------------------------------------------------------------------------
/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const proxy = require('http-proxy-middleware')
2 |
3 | const context = '/api'
4 |
5 | const options = {
6 | // By default, this is configured to proxy http://localhost:3000/api requests
7 | // to a running instance in localhost:3000, unless
8 | // process.env.LAUNCH_KIT_BACKEND_URL specify another thing (restart
9 | // create-react-app development server to read env var value)
10 | target: process.env.LAUNCH_KIT_BACKEND_URL
11 | ? process.env.LAUNCH_KIT_BACKEND_URL
12 | : 'http://localhost:3000',
13 | // rewrite paths
14 | pathRewrite: {
15 | '^/api': ''
16 | },
17 | changeOrigin: true
18 | }
19 |
20 | const apiProxy = proxy(context, options)
21 |
22 | module.exports = (app) => { app.use(apiProxy) }
23 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import 'jest-enzyme';
2 | import { configure } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 |
5 | configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/src/store/actions.ts:
--------------------------------------------------------------------------------
1 | import { getWeb3Wrapper } from '../services/web3_wrapper';
2 | import { getKnownTokens } from '../util/known_tokens';
3 | import { MARKETPLACES } from '../util/types';
4 |
5 | import { updateGasInfo, updateTokenBalances } from './blockchain/actions';
6 | import { getAllCollectibles } from './collectibles/actions';
7 | import { fetchMarkets, setMarketTokens, updateMarketPriceEther } from './market/actions';
8 | import { getOrderBook, getOrderbookAndUserOrders } from './relayer/actions';
9 | import { getCurrencyPair, getCurrentMarketPlace } from './selectors';
10 |
11 | export * from './blockchain/actions';
12 | export * from './market/actions';
13 | export * from './relayer/actions';
14 | export * from './router/actions';
15 | export * from './ui/actions';
16 | export * from './market/actions';
17 | export * from './collectibles/actions';
18 |
19 | export const updateStore = () => {
20 | return async (dispatch: any, getState: any) => {
21 | const state = getState();
22 | const web3Wrapper = await getWeb3Wrapper();
23 | const [ethAccount] = await web3Wrapper.getAvailableAddressesAsync();
24 | dispatch(updateTokenBalances());
25 | dispatch(updateGasInfo());
26 | dispatch(updateMarketPriceEther());
27 |
28 | // Updates based on the current app
29 | const currentMarketPlace = getCurrentMarketPlace(state);
30 | if (currentMarketPlace === MARKETPLACES.ERC20) {
31 | dispatch(updateERC20Store(ethAccount));
32 | } else {
33 | dispatch(updateERC721Store(ethAccount));
34 | }
35 | };
36 | };
37 |
38 | export const updateERC721Store = (ethAccount: string) => {
39 | return async (dispatch: any) => {
40 | dispatch(getAllCollectibles(ethAccount));
41 | };
42 | };
43 |
44 | export const updateERC20Store = (ethAccount: string) => {
45 | return async (dispatch: any, getState: any) => {
46 | const state = getState();
47 | try {
48 | const knownTokens = getKnownTokens();
49 | const currencyPair = getCurrencyPair(state);
50 | const baseToken = knownTokens.getTokenBySymbol(currencyPair.base);
51 | const quoteToken = knownTokens.getTokenBySymbol(currencyPair.quote);
52 |
53 | dispatch(setMarketTokens({ baseToken, quoteToken }));
54 | dispatch(getOrderbookAndUserOrders());
55 | await dispatch(fetchMarkets());
56 | } catch (error) {
57 | const knownTokens = getKnownTokens();
58 | const currencyPair = getCurrencyPair(state);
59 | const baseToken = knownTokens.getTokenBySymbol(currencyPair.base);
60 | const quoteToken = knownTokens.getTokenBySymbol(currencyPair.quote);
61 |
62 | dispatch(setMarketTokens({ baseToken, quoteToken }));
63 | dispatch(getOrderBook());
64 | }
65 | };
66 | };
67 |
--------------------------------------------------------------------------------
/src/store/blockchain/reducers.ts:
--------------------------------------------------------------------------------
1 | import { getType } from 'typesafe-actions';
2 |
3 | import { DEFAULT_ESTIMATED_TRANSACTION_TIME_MS, DEFAULT_GAS_PRICE, ZERO } from '../../common/constants';
4 | import { BlockchainState, ConvertBalanceState, Web3State } from '../../util/types';
5 | import * as actions from '../actions';
6 | import { RootAction } from '../reducers';
7 |
8 | const initialBlockchainState: BlockchainState = {
9 | ethAccount: '',
10 | web3State: Web3State.Loading,
11 | tokenBalances: [],
12 | ethBalance: ZERO,
13 | wethTokenBalance: null,
14 | gasInfo: {
15 | gasPriceInWei: DEFAULT_GAS_PRICE,
16 | estimatedTimeMs: DEFAULT_ESTIMATED_TRANSACTION_TIME_MS,
17 | },
18 | convertBalanceState: ConvertBalanceState.Success,
19 | };
20 |
21 | export function blockchain(state: BlockchainState = initialBlockchainState, action: RootAction): BlockchainState {
22 | switch (action.type) {
23 | case getType(actions.setEthAccount):
24 | return { ...state, ethAccount: action.payload };
25 | case getType(actions.setWeb3State):
26 | return { ...state, web3State: action.payload };
27 | case getType(actions.setTokenBalances):
28 | return { ...state, tokenBalances: action.payload };
29 | case getType(actions.setWethTokenBalance):
30 | return { ...state, wethTokenBalance: action.payload };
31 | case getType(actions.setGasInfo):
32 | return { ...state, gasInfo: action.payload };
33 | case getType(actions.setWethBalance):
34 | return {
35 | ...state,
36 | wethTokenBalance: state.wethTokenBalance
37 | ? {
38 | ...state.wethTokenBalance,
39 | balance: action.payload,
40 | }
41 | : null,
42 | };
43 | case getType(actions.setEthBalance):
44 | return { ...state, ethBalance: action.payload };
45 | case getType(actions.convertBalanceStateAsync.request):
46 | return { ...state, convertBalanceState: ConvertBalanceState.Request };
47 | case getType(actions.convertBalanceStateAsync.failure):
48 | return { ...state, convertBalanceState: ConvertBalanceState.Failure };
49 | case getType(actions.convertBalanceStateAsync.success):
50 | return { ...state, convertBalanceState: ConvertBalanceState.Success };
51 | case getType(actions.initializeBlockchainData):
52 | return {
53 | ...state,
54 | ...action.payload,
55 | };
56 | default:
57 | return state;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/store/collectibles/reducers.ts:
--------------------------------------------------------------------------------
1 | import { getType } from 'typesafe-actions';
2 |
3 | import { AllCollectiblesFetchStatus, Collectible, CollectiblesState } from '../../util/types';
4 | import * as actions from '../actions';
5 | import { RootAction } from '../reducers';
6 |
7 | const initialCollectibles: CollectiblesState = {
8 | collectibleSelected: null,
9 | allCollectibles: {},
10 | allCollectiblesFetchStatus: AllCollectiblesFetchStatus.Request,
11 | };
12 |
13 | export function collectibles(state: CollectiblesState = initialCollectibles, action: RootAction): CollectiblesState {
14 | switch (action.type) {
15 | case getType(actions.fetchAllCollectiblesAsync.success):
16 | const allCollectibles: { [key: string]: Collectible } = {};
17 | action.payload.collectibles.forEach(collectible => {
18 | allCollectibles[collectible.tokenId] = collectible;
19 | });
20 | const allCollectiblesFetchStatus = AllCollectiblesFetchStatus.Success;
21 | return { ...state, allCollectibles, allCollectiblesFetchStatus };
22 | case getType(actions.selectCollectible):
23 | return { ...state, collectibleSelected: action.payload };
24 | default:
25 | return state;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { routerMiddleware } from 'connected-react-router';
2 | import { createHashHistory } from 'history';
3 | import { AnyAction, applyMiddleware, compose, createStore } from 'redux';
4 | import thunk, { ThunkMiddleware } from 'redux-thunk';
5 |
6 | import { getCollectiblesMetadataGateway } from '../services/collectibles_metadata_gateway';
7 | import { getContractWrappers } from '../services/contract_wrappers';
8 | import { getWeb3Wrapper, initializeWeb3Wrapper } from '../services/web3_wrapper';
9 | import { StoreState } from '../util/types';
10 |
11 | import { localStorageMiddleware } from './middlewares';
12 | import { createRootReducer } from './reducers';
13 |
14 | export const history = createHashHistory();
15 | const rootReducer = createRootReducer(history);
16 |
17 | const extraArgument = {
18 | getContractWrappers,
19 | getWeb3Wrapper,
20 | initializeWeb3Wrapper,
21 | getCollectiblesMetadataGateway,
22 | };
23 | export type ExtraArgument = typeof extraArgument;
24 |
25 | const thunkMiddleware = thunk.withExtraArgument(extraArgument) as ThunkMiddleware;
26 |
27 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
28 | export const store = createStore(
29 | rootReducer,
30 | composeEnhancers(applyMiddleware(thunkMiddleware, localStorageMiddleware, routerMiddleware(history))),
31 | );
32 |
--------------------------------------------------------------------------------
/src/store/market/reducers.ts:
--------------------------------------------------------------------------------
1 | import queryString from 'query-string';
2 | import { getType } from 'typesafe-actions';
3 |
4 | import { availableMarkets } from '../../common/markets';
5 | import { MarketState } from '../../util/types';
6 | import * as actions from '../actions';
7 | import { RootAction } from '../reducers';
8 |
9 | const getMakerAddresses = () => {
10 | const makerAddressesString = queryString.parse(queryString.extract(window.location.hash)).makerAddresses as string;
11 | if (!makerAddressesString) {
12 | return null;
13 | }
14 | const makerAddresses = makerAddressesString.split(',');
15 | return makerAddresses.map(a => a.toLowerCase());
16 | };
17 |
18 | const initialMarketState: MarketState = {
19 | currencyPair: {
20 | base: (queryString.parse(queryString.extract(window.location.hash)).base as string) || availableMarkets[0].base,
21 | quote:
22 | (queryString.parse(queryString.extract(window.location.hash)).quote as string) || availableMarkets[0].quote,
23 | },
24 | baseToken: null,
25 | quoteToken: null,
26 | markets: null,
27 | ethInUsd: null,
28 | makerAddresses: getMakerAddresses(),
29 | };
30 |
31 | export function market(state: MarketState = initialMarketState, action: RootAction): MarketState {
32 | switch (action.type) {
33 | case getType(actions.setMarketTokens):
34 | return { ...state, baseToken: action.payload.baseToken, quoteToken: action.payload.quoteToken };
35 | case getType(actions.setCurrencyPair):
36 | return { ...state, currencyPair: action.payload };
37 | case getType(actions.setMarkets):
38 | return { ...state, markets: action.payload };
39 | case getType(actions.fetchMarketPriceEtherUpdate):
40 | return { ...state, ethInUsd: action.payload };
41 | case getType(actions.fetchMarketPriceEtherStart):
42 | return state;
43 | case getType(actions.fetchMarketPriceEtherError):
44 | return state;
45 | default:
46 | return state;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/store/middlewares.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
2 | import { getType } from 'typesafe-actions';
3 |
4 | import { LocalStorage } from '../services/local_storage';
5 |
6 | import * as actions from './actions';
7 | import { getEthAccount, getHasUnreadNotifications, getNotifications } from './selectors';
8 |
9 | const localStorage = new LocalStorage(window.localStorage);
10 |
11 | export const localStorageMiddleware: Middleware = ({ getState }: MiddlewareAPI) => (next: Dispatch) => (
12 | action: any,
13 | ) => {
14 | const result = next(action);
15 | switch (action.type) {
16 | case getType(actions.setHasUnreadNotifications):
17 | case getType(actions.addNotifications): {
18 | const state = getState();
19 | const ethAccount = getEthAccount(state);
20 | const notifications = getNotifications(state);
21 | const hasUnreadNotifications = getHasUnreadNotifications(state);
22 | localStorage.saveNotifications(notifications, ethAccount);
23 | localStorage.saveHasUnreadNotifications(hasUnreadNotifications, ethAccount);
24 | break;
25 | }
26 | case getType(actions.setNotifications): {
27 | const state = getState();
28 | const ethAccount = getEthAccount(state);
29 | const notifications = getNotifications(state);
30 | localStorage.saveNotifications(notifications, ethAccount);
31 |
32 | break;
33 | }
34 | default:
35 | return result;
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | import { connectRouter } from 'connected-react-router';
2 | import { History } from 'history';
3 | import { combineReducers } from 'redux';
4 | import { ActionType } from 'typesafe-actions';
5 |
6 | import { StoreState } from '../util/types';
7 |
8 | import * as actions from './actions';
9 | import { blockchain } from './blockchain/reducers';
10 | import { collectibles } from './collectibles/reducers';
11 | import { market } from './market/reducers';
12 | import { relayer } from './relayer/reducers';
13 | import { ui } from './ui/reducers';
14 |
15 | export type RootAction = ActionType;
16 |
17 | export const createRootReducer = (history: History) =>
18 | combineReducers({
19 | router: connectRouter(history),
20 | blockchain,
21 | relayer,
22 | ui,
23 | market,
24 | collectibles,
25 | });
26 |
--------------------------------------------------------------------------------
/src/store/relayer/reducers.ts:
--------------------------------------------------------------------------------
1 | import { getType } from 'typesafe-actions';
2 |
3 | import { RelayerState } from '../../util/types';
4 | import * as actions from '../actions';
5 | import { RootAction } from '../reducers';
6 |
7 | const initialRelayerState: RelayerState = {
8 | orders: [],
9 | userOrders: [],
10 | };
11 |
12 | export function relayer(state: RelayerState = initialRelayerState, action: RootAction): RelayerState {
13 | switch (action.type) {
14 | case getType(actions.setOrders):
15 | return { ...state, orders: action.payload };
16 | case getType(actions.setUserOrders):
17 | return { ...state, userOrders: action.payload };
18 | case getType(actions.initializeRelayerData):
19 | return action.payload;
20 | default:
21 | return state;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/styled.d.ts:
--------------------------------------------------------------------------------
1 | import 'styled-components';
2 |
3 | import { Theme } from './themes/commons';
4 |
5 | declare module 'styled-components' {
6 | interface DefaultTheme extends Theme {}
7 | }
8 |
--------------------------------------------------------------------------------
/src/tests/components/common/__snapshots__/show_number_with_colors.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ShowNumberWithColors should be initialized with value 123.0000 1`] = `
4 |
5 |
6 | 123
7 |
8 |
9 | .0000
10 |
11 |
12 | `;
13 |
14 | exports[`ShowNumberWithColors should be initialized with value 123.2200 1`] = `
15 |
16 |
17 | 123.22
18 |
19 |
20 | 00
21 |
22 |
23 | `;
24 |
--------------------------------------------------------------------------------
/src/tests/components/common/__snapshots__/toolbar.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Toolbar Toolbar to match snapshot 1`] = `
4 |
7 |
10 |
13 |
14 |
17 |
20 |
23 |
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/src/tests/components/common/icons/__snapshots__/token_icon.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`TokenIcon TokenIcon MLN to match snapshot 1`] = `
4 |
8 |
13 |
14 | `;
15 |
16 | exports[`TokenIcon TokenIcon ZRX to match snapshot 1`] = `
17 |
21 |
26 |
27 | `;
28 |
29 | exports[`TokenIcon Without token 1`] = `
30 |
34 |
39 |
40 | `;
41 |
--------------------------------------------------------------------------------
/src/tests/components/common/icons/token_icon.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import { ThemeProvider } from 'styled-components';
4 |
5 | import { TokenIcon } from '../../../../components/common/icons/token_icon';
6 | import { DefaultTheme } from '../../../../themes/default_theme';
7 |
8 | const theme = new DefaultTheme();
9 |
10 | describe('TokenIcon', () => {
11 | it('TokenIcon ZRX to match snapshot', () => {
12 | // given
13 | const symbol = 'zrx';
14 | const primaryColor = '#232332';
15 |
16 | const tokenIcon = (
17 |
18 |
19 |
20 | );
21 |
22 | // when
23 | const tree = renderer.create(tokenIcon).toJSON();
24 |
25 | // then
26 | expect(tree).toMatchSnapshot();
27 | });
28 |
29 | it('TokenIcon MLN to match snapshot', () => {
30 | // given
31 | const symbol = 'mln';
32 | const primaryColor = '#232332';
33 |
34 | const tokenIcon = (
35 |
36 |
37 |
38 | );
39 |
40 | // when
41 | const tree = renderer.create(tokenIcon).toJSON();
42 |
43 | // then
44 | expect(tree).toMatchSnapshot();
45 | });
46 |
47 | it('Without token', () => {
48 | // given
49 | const symbol = 'test';
50 | const primaryColor = '#232332';
51 |
52 | const tokenIcon = (
53 |
54 |
55 |
56 | );
57 |
58 | // when
59 | const tree = renderer.create(tokenIcon).toJSON();
60 |
61 | // then
62 | expect(tree).toMatchSnapshot();
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/tests/components/common/markets_dropdown.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import React from 'react';
3 |
4 | import { MarketsDropdownContainer } from '../../../components/erc20/common/markets_dropdown';
5 |
6 | describe('Markets Dropdown', () => {
7 | let wrapper;
8 |
9 | beforeEach(() => {
10 | wrapper = shallow();
11 | });
12 |
13 | it('Render the component', () => {
14 | expect(wrapper.length).toEqual(1);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/tests/components/common/pending_time.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import React from 'react';
3 |
4 | import { PendingTime } from '../../../components/common/pending_time';
5 |
6 | describe('PendingTime', () => {
7 | it('should show 00:05 when there are 5 seconds left', () => {
8 | // given
9 | const startTime = new Date(2019, 1, 1, 12, 29, 55);
10 | const estimatedTimeMs = 10000;
11 | const now = new Date(2019, 1, 1, 12, 30, 0);
12 |
13 | // when
14 | const wrapper = shallow();
15 |
16 | // then
17 | expect(wrapper.text()).toEqual('00:05 (Est. 10 seconds)');
18 | });
19 |
20 | it('should show 00:00 when the estimated time is already over', () => {
21 | // given
22 | const startTime = new Date(2019, 1, 1, 12, 29, 55);
23 | const estimatedTimeMs = 10000;
24 | const now = new Date(2019, 1, 1, 12, 31, 0);
25 |
26 | // when
27 | const wrapper = shallow();
28 |
29 | // then
30 | expect(wrapper.text()).toEqual('00:00 (Est. 10 seconds)');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/tests/components/common/show_number_with_colors.test.tsx:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 | import { shallow } from 'enzyme';
3 | import React from 'react';
4 |
5 | import { ShowNumberWithColors } from '../../../components/common/show_number_with_colors';
6 |
7 | describe('ShowNumberWithColors', () => {
8 | it('should be initialized with value 123.0000', () => {
9 | // given
10 | const value = new BigNumber('123');
11 |
12 | // when
13 | const wrapper = shallow();
14 |
15 | // then
16 | expect(wrapper).toMatchSnapshot();
17 | });
18 |
19 | it('should be initialized with value 123.2200', () => {
20 | // given
21 | const value = new BigNumber('123.22');
22 |
23 | // when
24 | const wrapper = shallow();
25 |
26 | // then
27 | expect(wrapper).toMatchSnapshot();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/tests/components/common/toolbar.test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 |
5 | import React from 'react';
6 | import renderer from 'react-test-renderer';
7 | import { ThemeProvider } from 'styled-components';
8 |
9 | import { Toolbar } from '../../../components/common/toolbar';
10 | import { DefaultTheme } from '../../../themes/default_theme';
11 | import { Web3State } from '../../../util/types';
12 |
13 | const theme = new DefaultTheme();
14 |
15 | describe('Toolbar', () => {
16 | it('Toolbar to match snapshot', () => {
17 | // given
18 | const startContent = (
19 | <>
20 |
21 | >
22 | );
23 |
24 | const endContent = (
25 | <>
26 |
27 | >
28 | );
29 |
30 | const toolbar = (
31 |
32 |
33 |
34 | );
35 |
36 | // when
37 | const tree = renderer.create(toolbar).toJSON();
38 |
39 | // then
40 | expect(tree).toMatchSnapshot();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/tests/components/erc721/collectibles/__snapshots__/owner_badge.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`OwnerBadge OwnerBadge to match snapshot 1`] = `
4 |
7 |
10 | You Own
11 |
12 |
13 | `;
14 |
--------------------------------------------------------------------------------
/src/tests/components/erc721/collectibles/owner_badge.test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 |
5 | import React from 'react';
6 | import renderer from 'react-test-renderer';
7 | import { ThemeProvider } from 'styled-components';
8 |
9 | import { OwnerBadge } from '../../../../components/erc721/collectibles/owner_badge';
10 | import { DefaultTheme } from '../../../../themes/default_theme';
11 |
12 | const theme = new DefaultTheme();
13 |
14 | describe('OwnerBadge', () => {
15 | it('OwnerBadge to match snapshot', () => {
16 | // given
17 | const ownerBadge = (
18 |
19 |
20 |
21 | );
22 |
23 | // when
24 | const tree = renderer.create(ownerBadge).toJSON();
25 |
26 | // then
27 | expect(tree).toMatchSnapshot();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/tests/util/logger.test.ts:
--------------------------------------------------------------------------------
1 | import { LOGGER_ID } from '../../common/constants';
2 | import { getLogger } from '../../util/logger';
3 |
4 | describe('Logger', () => {
5 | it('should check some prefix', async () => {
6 | // Given
7 | const logs = [
8 | {
9 | logger: getLogger('test'),
10 | expected: `${LOGGER_ID}::test`,
11 | },
12 | {
13 | logger: getLogger('test1'),
14 | expected: `${LOGGER_ID}::test1`,
15 | },
16 | {
17 | logger: getLogger('great'),
18 | expected: `${LOGGER_ID}::great`,
19 | },
20 | {
21 | logger: getLogger('max'),
22 | expected: `${LOGGER_ID}::max`,
23 | },
24 | ];
25 |
26 | for (const log of logs) {
27 | const { logger, expected } = log;
28 |
29 | // When
30 | // @ts-ignore
31 | const prefix = logger.opts.prefix;
32 |
33 | // Then
34 | expect(prefix).toEqual(expected);
35 | }
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/tests/util/number_utils.test.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { ZERO } from '../../common/constants';
4 | import { padRightSplitted } from '../../util/number_utils';
5 |
6 | describe('padRightSplitted', () => {
7 | it('should test some numbers', async () => {
8 | // Given
9 | const numbers = [
10 | {
11 | num: new BigNumber(0.1),
12 | decimals: 4,
13 | expectedDiff: '000',
14 | expectedNum: '0.1',
15 | },
16 | {
17 | num: new BigNumber(0.02),
18 | decimals: 4,
19 | expectedDiff: '00',
20 | expectedNum: '0.02',
21 | },
22 | {
23 | num: new BigNumber(4),
24 | decimals: 4,
25 | expectedDiff: '.0000',
26 | expectedNum: '4',
27 | },
28 | {
29 | num: new BigNumber(2478),
30 | decimals: 4,
31 | expectedDiff: '.0000',
32 | expectedNum: '2478',
33 | },
34 | {
35 | num: new BigNumber(10.1234),
36 | decimals: 4,
37 | expectedDiff: '',
38 | expectedNum: '10.1234',
39 | },
40 | {
41 | num: ZERO,
42 | decimals: 4,
43 | expectedDiff: '',
44 | expectedNum: '0.0000',
45 | },
46 | {
47 | num: new BigNumber(100.676767),
48 | decimals: 4,
49 | expectedDiff: '',
50 | expectedNum: '100.6768',
51 | },
52 | {
53 | num: ZERO,
54 | decimals: 4,
55 | expectedDiff: '',
56 | expectedNum: '0.0000',
57 | },
58 | ];
59 |
60 | for (const numberObject of numbers) {
61 | const { num, decimals } = numberObject;
62 |
63 | // When
64 | const result = padRightSplitted(num, decimals);
65 |
66 | // Then
67 | expect(result.num).toEqual(numberObject.expectedNum);
68 | expect(result.diff).toEqual(numberObject.expectedDiff);
69 | }
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/src/tests/util/test_with_theme.tsx:
--------------------------------------------------------------------------------
1 | import { mount, shallow } from 'enzyme';
2 | import React from 'react';
3 | import renderer from 'react-test-renderer';
4 | import { ThemeProvider } from 'styled-components';
5 |
6 | import { DefaultTheme } from '../../themes/default_theme';
7 |
8 | const theme = new DefaultTheme();
9 |
10 | export const mountWithTheme = (children: React.ReactElement) =>
11 | mount({children});
12 |
13 | export const renderWithTheme = (children: any) =>
14 | renderer.create({children}).toJSON();
15 |
16 | export const shallowWithTheme = (children: any) => shallow({children});
17 |
--------------------------------------------------------------------------------
/src/tests/util/tokens.test.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { tokenAmountInUnits, tokenSymbolToDisplayString, unitsInTokenAmount } from '../../util/tokens';
4 |
5 | describe('tokenAmountInUnits', () => {
6 | it('should format the token amount', async () => {
7 | // given
8 | const amount = new BigNumber('123');
9 | const decimals = 2;
10 |
11 | // when
12 | const result = tokenAmountInUnits(amount, decimals);
13 |
14 | // then
15 | expect(result).toEqual('1.23');
16 | });
17 |
18 | it('should have 2 digits of precision', async () => {
19 | // given
20 | const amount = new BigNumber('12345');
21 | const decimals = 4;
22 |
23 | // when
24 | const result = tokenAmountInUnits(amount, decimals);
25 |
26 | // then
27 | expect(result).toEqual('1.23');
28 | });
29 | });
30 |
31 | describe('unitsInTokenAmount', () => {
32 | it('should convert the given amount to a BigNumber', async () => {
33 | // given
34 | const amount = '1.23';
35 | const decimals = 2;
36 |
37 | // when
38 | const result = unitsInTokenAmount(amount, decimals);
39 |
40 | // then
41 | const expected = new BigNumber('123');
42 | expect(result.eq(expected)).toBe(true);
43 | });
44 |
45 | it('should take decimals into account', async () => {
46 | // given
47 | const amount = '1.23';
48 | const decimals = 4;
49 |
50 | // when
51 | const result = unitsInTokenAmount(amount, decimals);
52 |
53 | // then
54 | const expected = new BigNumber('12300');
55 | expect(result.eq(expected)).toBe(true);
56 | });
57 | });
58 |
59 | describe('tokenSymbolToDisplayString', () => {
60 | it('should return weth token correctly formated', async () => {
61 | // given
62 | const symbol = 'weth';
63 | // when
64 | const result = tokenSymbolToDisplayString(symbol);
65 |
66 | // then
67 | const expected = 'wETH';
68 | expect(result === expected).toBe(true);
69 | });
70 |
71 | it('should return generic token (no weth) in uppercase', async () => {
72 | // given
73 | const symbol = 'zrx';
74 | // when
75 | const result = tokenSymbolToDisplayString(symbol);
76 |
77 | // then
78 | const expected = 'ZRX';
79 | expect(result === expected).toBe(true);
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/src/themes/theme_meta_data.ts:
--------------------------------------------------------------------------------
1 | import { ThemeMetaData } from './commons';
2 | import { DarkTheme } from './dark_theme';
3 | import { DefaultTheme } from './default_theme';
4 |
5 | export const KNOWN_THEMES_META_DATA: ThemeMetaData[] = [
6 | {
7 | name: 'LIGHT_THEME',
8 | theme: new DefaultTheme(),
9 | },
10 | {
11 | name: 'DARK_THEME',
12 | theme: new DarkTheme(),
13 | },
14 | ];
15 |
--------------------------------------------------------------------------------
/src/themes/theme_meta_data_utils.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '../common/config';
2 | import { ERC20_THEME_NAME, ERC721_THEME_NAME } from '../common/constants';
3 | import { getLogger } from '../util/logger';
4 | import { MARKETPLACES } from '../util/types';
5 |
6 | import { Theme } from './commons';
7 | import { DefaultTheme } from './default_theme';
8 | import { KNOWN_THEMES_META_DATA } from './theme_meta_data';
9 |
10 | const logger = getLogger('Themes::theme_meta_data.ts');
11 |
12 | const getThemeByName = (themeName: string): Theme => {
13 | const themeDataFetched = KNOWN_THEMES_META_DATA.find(themeMetaData => themeMetaData.name === themeName);
14 | let themeReturn = null;
15 | if (!themeDataFetched) {
16 | logger.error(`Theme with name ${themeName} not found`);
17 | themeReturn = new DefaultTheme();
18 | } else {
19 | themeReturn = themeDataFetched.theme;
20 | }
21 | return themeReturn;
22 | };
23 |
24 | export const getThemeByMarketplace = (marketplace: MARKETPLACES): Theme => {
25 | const themeBase =
26 | marketplace === MARKETPLACES.ERC20 ? getThemeByName(ERC20_THEME_NAME) : getThemeByName(ERC721_THEME_NAME);
27 | const themeConfig = Config.getConfig().theme;
28 | const componentsTheme = themeConfig
29 | ? { ...themeBase.componentsTheme, ...themeConfig.componentsTheme }
30 | : themeBase.componentsTheme;
31 | const modalTheme = themeConfig
32 | ? {
33 | content: {
34 | ...themeBase.modalTheme.content,
35 | ...(themeConfig.modalTheme && themeConfig.modalTheme.content),
36 | },
37 | overlay: {
38 | ...themeBase.modalTheme.overlay,
39 | ...(themeConfig.modalTheme && themeConfig.modalTheme.overlay),
40 | },
41 | }
42 | : themeBase.modalTheme;
43 | return {
44 | componentsTheme,
45 | modalTheme,
46 | };
47 | };
48 |
--------------------------------------------------------------------------------
/src/util/cancelable_promises.ts:
--------------------------------------------------------------------------------
1 | export interface CancelablePromise {
2 | promise: Promise;
3 | cancel: () => void;
4 | }
5 |
6 | export function makeCancelable(promise: Promise): CancelablePromise {
7 | let hasCanceled = false;
8 |
9 | const wrappedPromise = new Promise((resolve, reject) => {
10 | promise.then(
11 | val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)),
12 | error => (hasCanceled ? reject({ isCanceled: true }) : reject(error)),
13 | );
14 | });
15 |
16 | return {
17 | promise: wrappedPromise,
18 | cancel: () => {
19 | hasCanceled = true;
20 | },
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/util/collectibles.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { Collectible } from './types';
4 |
5 | export const getCollectiblePrice = (collectible: Collectible): BigNumber | null => {
6 | const { order } = collectible;
7 | if (order === null) {
8 | return null;
9 | }
10 | return order.takerAssetAmount;
11 |
12 | // try {
13 | // const dutchAcutionData = getDutchAuctionData(order.makerAssetData);
14 | // const { beginAmount, beginTimeSeconds } = dutchAcutionData;
15 | // const endAmount = order.takerAssetAmount;
16 | // const startTimeSeconds = order.expirationTimeSeconds;
17 | // // Use y = mx + b (linear function)
18 | // const m = endAmount.minus(beginAmount).dividedBy(startTimeSeconds.minus(beginTimeSeconds));
19 | // const b = beginAmount.minus(beginTimeSeconds.multipliedBy(m));
20 | // return m.multipliedBy(todayInSeconds()).plus(b);
21 | // } catch (err) {
22 | // return order.takerAssetAmount;
23 | // }
24 | };
25 |
--------------------------------------------------------------------------------
/src/util/error_messages.ts:
--------------------------------------------------------------------------------
1 | import { NETWORK_NAME } from '../common/constants';
2 |
3 | export const errorsBuySell = {
4 | ethLack: 'You don’t have enough ETH...',
5 | zrxLack: 'You don’t have enough ZRX to pay fees...',
6 | };
7 |
8 | export const errorsWallet = {
9 | mmLoading: 'Please wait while we load your wallet',
10 | mmConnect: 'Click to Connect MetaMask',
11 | mmLocked: 'Metamask Locked',
12 | mmNotInstalled: 'Metamask not installed',
13 | mmGetExtension: 'Get Chrome Extension ',
14 | mmWrongNetwork: `Wrong network: switch to ${NETWORK_NAME}`,
15 | };
16 |
17 | // Receives an string with an error JSON object an returns the JSON Object or null if does not exist
18 | export const getErrorResponseFrom0xConnectErrorMessage = (str: string) => {
19 | const firstOpen = str.indexOf('{');
20 | const lastClose = str.lastIndexOf('}') + 1;
21 | let candidate;
22 | if (lastClose <= firstOpen) {
23 | return null;
24 | }
25 | try {
26 | candidate = str.substring(firstOpen, lastClose);
27 | return JSON.parse(candidate);
28 | } catch (e) {
29 | return null;
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/src/util/filterable_collectibles.ts:
--------------------------------------------------------------------------------
1 | import { isDutchAuction } from './orders';
2 | import { SortableCollectible } from './sortable_collectibles';
3 | import { Collectible } from './types';
4 |
5 | export enum CollectibleFilterType {
6 | ShowAll = 'show_all',
7 | FixedPrice = 'fixed_price',
8 | DecliningAuction = 'declining_auction',
9 | }
10 |
11 | const isCollectibleSoldInDutchAuction = (collectible: Collectible): boolean => {
12 | if (collectible.order === null) {
13 | return false;
14 | }
15 | return isDutchAuction(collectible.order);
16 | };
17 |
18 | const isCollectibleSoldInBasicSell = (collectible: Collectible): boolean => {
19 | if (collectible.order === null) {
20 | return false;
21 | }
22 | return !isDutchAuction(collectible.order);
23 | };
24 |
25 | export const getFilterFunction = (filterType: CollectibleFilterType): ((sc: SortableCollectible) => boolean) => {
26 | switch (filterType) {
27 | case CollectibleFilterType.DecliningAuction:
28 | return (sc: SortableCollectible) => isCollectibleSoldInDutchAuction(sc.collectible);
29 | case CollectibleFilterType.FixedPrice:
30 | return (sc: SortableCollectible) => isCollectibleSoldInBasicSell(sc.collectible);
31 | default:
32 | return () => true;
33 | }
34 | };
35 |
36 | export const getFilteredCollectibles = (
37 | collectibles: SortableCollectible[],
38 | filterType: CollectibleFilterType,
39 | ): SortableCollectible[] => {
40 | const filterFunction = getFilterFunction(filterType);
41 | return collectibles.filter(filterFunction);
42 | };
43 |
44 | export const filterCollectibleByName = (collectibles: Collectible[], name: string): Collectible[] => {
45 | return collectibles.filter(collectible => {
46 | const collectibleName = (collectible.name || '').toLowerCase();
47 | return collectibleName.indexOf(name.toLowerCase()) > -1;
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/src/util/logger.ts:
--------------------------------------------------------------------------------
1 | import logdown from 'logdown';
2 |
3 | import { LOGGER_ID } from '../common/constants';
4 |
5 | export const getLogger = (title: string) => logdown(`${LOGGER_ID}::${title}`);
6 |
--------------------------------------------------------------------------------
/src/util/markets.ts:
--------------------------------------------------------------------------------
1 | import { Market } from './types';
2 |
3 | export const filterMarketsByTokenSymbol = (markets: Market[], tokenSymbol: string): Market[] => {
4 | return markets.filter(
5 | market => market.currencyPair.base === tokenSymbol || market.currencyPair.quote === tokenSymbol,
6 | );
7 | };
8 |
9 | export const filterMarketsByString = (markets: Market[], str: string): Market[] => {
10 | return markets.filter(market => {
11 | const baseLowerCase = market.currencyPair.base.toLowerCase();
12 | const quoteLowerCase = market.currencyPair.quote.toLowerCase();
13 | return `${baseLowerCase}/${quoteLowerCase}`.indexOf(str.toLowerCase()) !== -1;
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/src/util/number_utils.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { UI_DECIMALS_DISPLAYED_ORDER_SIZE } from '../common/constants';
4 |
5 | export const padRightSplitted = (
6 | numBg: BigNumber,
7 | decimals: number = UI_DECIMALS_DISPLAYED_ORDER_SIZE,
8 | ): { num: string; diff: string } => {
9 | const numBgToFixed = numBg.toFixed(decimals);
10 | const numBgToString = numBg.toString();
11 |
12 | const decimalPlaces = (numBgToString.split('.')[1] || []).length;
13 |
14 | let diff = '';
15 | let num = numBgToFixed;
16 | if (!numBg.isZero() && decimalPlaces < decimals) {
17 | diff = numBgToFixed.replace(numBgToString, '');
18 | num = numBgToString;
19 | }
20 |
21 | return {
22 | num,
23 | diff,
24 | };
25 | };
26 |
27 | export const truncateAddress = (address: string) => {
28 | return `${address.slice(0, 7)}...${address.slice(address.length - 5)}`;
29 | };
30 |
--------------------------------------------------------------------------------
/src/util/sleep.ts:
--------------------------------------------------------------------------------
1 | export const sleep = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout));
2 |
--------------------------------------------------------------------------------
/src/util/steps.ts:
--------------------------------------------------------------------------------
1 | import { OrderSide, Step, StepKind } from './types';
2 |
3 | export const getStepTitle = (step: Step): string => {
4 | switch (step.kind) {
5 | case StepKind.SellCollectible:
6 | case StepKind.BuySellLimit:
7 | return 'Sign';
8 | case StepKind.BuySellMarket:
9 | return step.side === OrderSide.Buy ? 'Buy' : 'Sell';
10 | case StepKind.ToggleTokenLock:
11 | case StepKind.UnlockCollectibles:
12 | return step.isUnlocked ? 'Lock' : 'Unlock';
13 | case StepKind.WrapEth:
14 | return 'Convert';
15 | case StepKind.BuyCollectible:
16 | return 'Buy';
17 | default:
18 | const _exhaustiveCheck: never = step;
19 | return _exhaustiveCheck;
20 | }
21 | };
22 |
23 | export const isLongStep = (step: Step): boolean => {
24 | switch (step.kind) {
25 | case StepKind.SellCollectible:
26 | case StepKind.BuySellLimit:
27 | return false;
28 | case StepKind.BuySellMarket:
29 | case StepKind.ToggleTokenLock:
30 | case StepKind.UnlockCollectibles:
31 | case StepKind.WrapEth:
32 | case StepKind.BuyCollectible:
33 | return true;
34 | default:
35 | const _exhaustiveCheck: never = step;
36 | return _exhaustiveCheck;
37 | }
38 | };
39 |
40 | export const makeGetProgress = (beginning: number, estimatedTxTimeMs: number) => (now: number) => {
41 | const elapsedMs = now - beginning;
42 |
43 | const progress = Math.round((elapsedMs / estimatedTxTimeMs) * 100);
44 |
45 | return Math.max(0, Math.min(progress, 95));
46 | };
47 |
--------------------------------------------------------------------------------
/src/util/time_utils.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { DEFAULT_ORDER_EXPIRY_SECONDS } from '../common/constants';
4 |
5 | export const tomorrow = () => {
6 | return new BigNumber(Math.floor(new Date().valueOf() / 1000) + 3600 * 24);
7 | };
8 |
9 | export const todayInSeconds = () => {
10 | return Math.floor(Date.now() / 1000);
11 | };
12 |
13 | export const convertTimeInSecondsToDaysAndHours = (timeInSeconds: BigNumber) => {
14 | let seconds = timeInSeconds.toNumber();
15 | const days = Math.floor(seconds / (3600 * 24));
16 | seconds -= days * 3600 * 24;
17 | const hours = Math.floor(seconds / 3600);
18 | return {
19 | days,
20 | hours,
21 | };
22 | };
23 |
24 | export const getExpirationTimeOrdersFromConfig = () => {
25 | return new BigNumber(todayInSeconds()).plus(DEFAULT_ORDER_EXPIRY_SECONDS);
26 | };
27 |
28 | export const getEndDateStringFromTimeInSeconds = (timeInSeconds: BigNumber) => {
29 | const currentDate = new Date(timeInSeconds.toNumber() * 1000);
30 | return currentDate.toLocaleString('en-us');
31 | };
32 |
--------------------------------------------------------------------------------
/src/util/token_meta_data.ts:
--------------------------------------------------------------------------------
1 | import { NETWORK_ID, UI_DECIMALS_DISPLAYED_DEFAULT_PRECISION } from '../common/constants';
2 | import { TokenMetaData } from '../common/tokens_meta_data';
3 |
4 | import { Token } from './types';
5 |
6 | export const getWethTokenFromTokensMetaDataByNetworkId = (tokensMetaData: TokenMetaData[]): Token => {
7 | const tokenMetaData = tokensMetaData.find(t => t.symbol === 'weth');
8 | if (!tokenMetaData) {
9 | throw new Error('WETH Token MetaData not found');
10 | }
11 | return {
12 | address: tokenMetaData.addresses[NETWORK_ID],
13 | symbol: tokenMetaData.symbol,
14 | decimals: tokenMetaData.decimals,
15 | name: tokenMetaData.name,
16 | primaryColor: tokenMetaData.primaryColor,
17 | icon: tokenMetaData.icon,
18 | displayDecimals: tokenMetaData.displayDecimals || UI_DECIMALS_DISPLAYED_DEFAULT_PRECISION,
19 | };
20 | };
21 |
22 | export const mapTokensMetaDataToTokenByNetworkId = (tokensMetaData: TokenMetaData[]): Token[] => {
23 | return tokensMetaData
24 | .filter(tokenMetaData => tokenMetaData.addresses[NETWORK_ID])
25 | .map(
26 | (tokenMetaData): Token => {
27 | return {
28 | address: tokenMetaData.addresses[NETWORK_ID],
29 | symbol: tokenMetaData.symbol,
30 | decimals: tokenMetaData.decimals,
31 | name: tokenMetaData.name,
32 | primaryColor: tokenMetaData.primaryColor,
33 | icon: tokenMetaData.icon,
34 | displayDecimals: tokenMetaData.displayDecimals || UI_DECIMALS_DISPLAYED_DEFAULT_PRECISION,
35 | };
36 | },
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/util/tokens.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 |
3 | import { isWeth } from './known_tokens';
4 |
5 | export const tokenAmountInUnitsToBigNumber = (amount: BigNumber, decimals: number): BigNumber => {
6 | const decimalsPerToken = new BigNumber(10).pow(decimals);
7 | return amount.div(decimalsPerToken);
8 | };
9 |
10 | export const tokenAmountInUnits = (amount: BigNumber, decimals: number, toFixedDecimals = 2): string => {
11 | return tokenAmountInUnitsToBigNumber(amount, decimals).toFixed(toFixedDecimals);
12 | };
13 |
14 | export const unitsInTokenAmount = (units: string, decimals: number): BigNumber => {
15 | const decimalsPerToken = new BigNumber(10).pow(decimals);
16 |
17 | return new BigNumber(units).multipliedBy(decimalsPerToken);
18 | };
19 |
20 | export const tokenSymbolToDisplayString = (symbol: string): string => {
21 | return isWeth(symbol) ? 'wETH' : symbol.toUpperCase();
22 | };
23 |
--------------------------------------------------------------------------------
/src/util/transaction_link.ts:
--------------------------------------------------------------------------------
1 | import { NETWORK_ID } from '../common/constants';
2 | import { Network } from '../util/types';
3 |
4 | const ETHERSCAN_TRANSACTION_URL: { [key: number]: string } = {
5 | [Network.Mainnet]: 'https://etherscan.io/tx/',
6 | [Network.Rinkeby]: 'https://rinkeby.etherscan.io/tx/',
7 | [Network.Kovan]: 'https://kovan.etherscan.io/tx/',
8 | [Network.Ganache]: 'https://etherscan.io/tx/',
9 | };
10 |
11 | export const getTransactionLink = (hash: string): string => {
12 | return `${ETHERSCAN_TRANSACTION_URL[NETWORK_ID]}${hash}`;
13 | };
14 |
--------------------------------------------------------------------------------
/src/util/transactions.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@0x/utils';
2 | import retry from 'async-retry';
3 |
4 | import { TX_DEFAULTS } from '../common/constants';
5 | import { getWeb3Wrapper } from '../services/web3_wrapper';
6 |
7 | const GET_BLOCK_NUMBER_FROM_TRANSACTION_HASH_RETRIES = 10;
8 |
9 | export const getTransactionOptions = (gasPrice: BigNumber) => {
10 | let options = {
11 | gasPrice,
12 | };
13 |
14 | if (process.env.NODE_ENV === 'development') {
15 | options = {
16 | ...options,
17 | ...TX_DEFAULTS,
18 | };
19 | }
20 |
21 | return options;
22 | };
23 |
24 | export const getBlockNumberFromTransactionHash = async (txHash: string | undefined): Promise => {
25 | try {
26 | if (!txHash) {
27 | throw new Error('Transaction hash was not provided');
28 | }
29 |
30 | const web3Wrapper = await getWeb3Wrapper();
31 | return retry(
32 | async () => {
33 | const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
34 | if (transaction.blockNumber === null) {
35 | throw new Error('retry');
36 | }
37 | return transaction.blockNumber || undefined;
38 | },
39 | {
40 | retries: GET_BLOCK_NUMBER_FROM_TRANSACTION_HASH_RETRIES,
41 | },
42 | );
43 | } catch (err) {
44 | return Promise.resolve(undefined);
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "noEmit": true,
15 | "jsx": "preserve",
16 | "isolatedModules": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@0x/tslint-config"],
3 | "rules": {
4 | "custom-no-magic-numbers": false,
5 | "semicolon": [true, "always", "ignore-bound-class-methods"],
6 | "max-classes-per-file": false,
7 | "switch-default": false,
8 | "no-unnecessary-type-assertion": false,
9 | "completed-docs": false,
10 | "promise-function-async": false,
11 | "interface-name": [true, "never-prefix"]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------