|
@ -4,6 +4,5 @@
|
|||
"useTabs": false,
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma":
|
||||
"none"
|
||||
"trailingComma": "none"
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ install:
|
|||
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script: npm run prettier:diff
|
||||
- stage: test
|
||||
script: npm run test
|
||||
- stage: test
|
||||
|
|
168
README.md
|
@ -39,22 +39,25 @@ npm run dev:https
|
|||
```
|
||||
|
||||
#### Address Derivation Checker:
|
||||
|
||||
EthereumJS-Util previously contained a bug that would incorrectly derive addresses from private keys with a 1/128 probability of occurring. A summary of this issue can be found [here](https://www.reddit.com/r/ethereum/comments/48rt6n/using_myetherwalletcom_just_burned_me_for/d0m4c6l/).
|
||||
|
||||
As a reactionary measure, the address derivation checker was created.
|
||||
As a reactionary measure, the address derivation checker was created.
|
||||
|
||||
To test for correct address derivation, the address derivation checker uses multiple sources of address derivation (EthereumJS and PyEthereum) to ensure that multiple official implementations derive the same address for any given private key.
|
||||
|
||||
##### The derivation checker utility assumes that you have:
|
||||
|
||||
1. Docker installed/available
|
||||
2. [dternyak/eth-priv-to-addr](https://hub.docker.com/r/dternyak/eth-priv-to-addr/) pulled from DockerHub
|
||||
|
||||
##### Docker setup instructions:
|
||||
|
||||
1. Install docker (on macOS, [Docker for Mac](https://docs.docker.com/docker-for-mac/) is suggested)
|
||||
2. `docker pull dternyak/eth-priv-to-addr`
|
||||
|
||||
|
||||
##### Run Derivation Checker
|
||||
|
||||
The derivation checker utility runs as part of the integration test suite.
|
||||
|
||||
```bash
|
||||
|
@ -84,7 +87,6 @@ npm run test:int
|
|||
|
||||
The following are guides for developers to follow for writing compliant code.
|
||||
|
||||
|
||||
### Redux and Actions
|
||||
|
||||
Each reducer has one file in `reducers/[namespace].ts` that contains the reducer
|
||||
|
@ -116,7 +118,7 @@ export function [namespace](
|
|||
return {
|
||||
...state,
|
||||
// Alterations to state
|
||||
};
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -124,11 +126,12 @@ export function [namespace](
|
|||
```
|
||||
|
||||
#### Actions
|
||||
|
||||
* Define each action creator in `actionCreator.ts`
|
||||
* Define each action object type in `actionTypes.ts`
|
||||
* Export a union of all of the action types for use by the reducer
|
||||
* Define each action type as a string enum in `constants.ts`
|
||||
* Export `actionCreators` and `actionTypes` from module file `index.ts`
|
||||
* Export a union of all of the action types for use by the reducer
|
||||
* Define each action type as a string enum in `constants.ts`
|
||||
* Export `actionCreators` and `actionTypes` from module file `index.ts`
|
||||
|
||||
```
|
||||
├── common
|
||||
|
@ -139,27 +142,30 @@ export function [namespace](
|
|||
├── constants.ts - string enum
|
||||
├── index.ts - exports all action creators and action object types
|
||||
```
|
||||
|
||||
##### constants.ts
|
||||
|
||||
```ts
|
||||
export enum TypeKeys {
|
||||
NAMESPACE_NAME_OF_ACTION = 'NAMESPACE_NAME_OF_ACTION'
|
||||
}
|
||||
```
|
||||
|
||||
##### actionTypes.ts
|
||||
|
||||
```ts
|
||||
/*** Name of action ***/
|
||||
export interface NameOfActionAction {
|
||||
type: TypeKeys.NAMESPACE_NAME_OF_ACTION,
|
||||
/* Rest of the action object shape */
|
||||
};
|
||||
type: TypeKeys.NAMESPACE_NAME_OF_ACTION;
|
||||
/* Rest of the action object shape */
|
||||
}
|
||||
|
||||
/*** Action Union ***/
|
||||
export type NamespaceAction =
|
||||
| ActionOneAction
|
||||
| ActionTwoAction
|
||||
| ActionThreeAction;
|
||||
export type NamespaceAction = ActionOneAction | ActionTwoAction | ActionThreeAction;
|
||||
```
|
||||
|
||||
##### actionCreators.ts
|
||||
|
||||
```ts
|
||||
import * as interfaces from './actionTypes';
|
||||
import { TypeKeys } from './constants';
|
||||
|
@ -172,7 +178,9 @@ export function nameOfAction(): interfaces.NameOfActionAction {
|
|||
};
|
||||
};
|
||||
```
|
||||
|
||||
##### index.ts
|
||||
|
||||
```ts
|
||||
export * from './actionCreators';
|
||||
export * from './actionTypes';
|
||||
|
@ -215,60 +223,34 @@ conditional render.)
|
|||
### Higher Order Components
|
||||
|
||||
#### Typing Injected Props
|
||||
Props made available through higher order components can be tricky to type. Normally, if a component requires a prop, you add it to the component's interface and it just works. However, working with injected props from [higher order components](https://medium.com/@DanHomola/react-higher-order-components-in-typescript-made-simple-6f9b55691af1), you will be forced to supply all required props whenever you compose the component.
|
||||
|
||||
Props made available through higher order components can be tricky to type. You can inherit the injected props, and in the case of react router, specialize the generic in `withRouter` so it can omit all of its injected props from the component.
|
||||
|
||||
```ts
|
||||
interface MyComponentProps {
|
||||
name: string;
|
||||
countryCode?: string;
|
||||
routerLocation: { pathname: string };
|
||||
}
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
...
|
||||
|
||||
class OtherComponent extends React.Component<{}, {}> {
|
||||
render() {
|
||||
return (
|
||||
<MyComponent
|
||||
name="foo"
|
||||
countryCode="CA"
|
||||
// Error: 'routerLocation' is missing!
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Instead of tacking the injected props on the MyComponentProps interface, put them in another interface called `InjectedProps`:
|
||||
|
||||
```ts
|
||||
interface MyComponentProps {
|
||||
interface MyComponentProps extends RouteComponentProps<{}> {
|
||||
name: string;
|
||||
countryCode?: string;
|
||||
}
|
||||
|
||||
interface InjectedProps {
|
||||
routerLocation: { pathname: string };
|
||||
}
|
||||
```
|
||||
|
||||
Now add a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) to cast `this.props` as the original props - `MyComponentProps` and the injected props - `InjectedProps`:
|
||||
|
||||
```ts
|
||||
class MyComponent extends React.Component<MyComponentProps, {}> {
|
||||
get injected() {
|
||||
return this.props as MyComponentProps & InjectedProps;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, countryCode, routerLocation } = this.props;
|
||||
const { name, countryCode, location } = this.props; // location being the one of the injected props from the withRouter HOC
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter<Props>(MyComponent);
|
||||
```
|
||||
|
||||
## Event Handlers
|
||||
|
||||
Event handlers such as `onChange` and `onClick`, should be properly typed. For example, if you have an event listener on an input element inside a form:
|
||||
|
||||
```ts
|
||||
public onValueChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
if (this.props.onChange) {
|
||||
|
@ -279,6 +261,7 @@ public onValueChange = (e: React.FormEvent<HTMLInputElement>) => {
|
|||
}
|
||||
};
|
||||
```
|
||||
|
||||
Where you type the event as a `React.FormEvent` of type `HTML<TYPE>Element`.
|
||||
|
||||
## Class names
|
||||
|
@ -292,18 +275,18 @@ However, going forward, each styled component should create a a `.scss` file of
|
|||
the same name in the same folder, and import it like so:
|
||||
|
||||
```ts
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import "./MyComponent.scss";
|
||||
import './MyComponent.scss';
|
||||
|
||||
export default class MyComponent extends React.component<{}, {}> {
|
||||
render() {
|
||||
return (
|
||||
<div className="MyComponent">
|
||||
<div className="MyComponent-child">Hello!</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="MyComponent">
|
||||
<div className="MyComponent-child">Hello!</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -311,15 +294,15 @@ These style modules adhere to [SuitCSS naming convention](https://github.com/sui
|
|||
|
||||
```scss
|
||||
.MyComponent {
|
||||
/* Styles */
|
||||
/* Styles */
|
||||
|
||||
&-child {
|
||||
/* Styles */
|
||||
&-child {
|
||||
/* Styles */
|
||||
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -329,10 +312,10 @@ create a new namespace (Potentially breaking that out into its own component.)
|
|||
Variables and mixins can be imported from the files in `common/styles`:
|
||||
|
||||
```scss
|
||||
@import "sass/colors";
|
||||
@import 'sass/colors';
|
||||
|
||||
code {
|
||||
color: $code-color;
|
||||
color: $code-color;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -350,35 +333,36 @@ When working on a module that has styling in Less, try to do the following:
|
|||
* Ensure that there has been little to no deviation from screenshot
|
||||
|
||||
#### Adding Icon-fonts
|
||||
|
||||
1. Download chosen icon-font
|
||||
1. Declare css font-family:
|
||||
```
|
||||
@font-face {
|
||||
font-family: 'social-media';
|
||||
src: url('../assets/fonts/social-media.eot');
|
||||
src: url('../assets/fonts/social-media.eot') format('embedded-opentype'),
|
||||
url('../assets/fonts/social-media.woff2') format('woff2'),
|
||||
url('../assets/fonts/social-media.woff') format('woff'),
|
||||
url('../assets/fonts/social-media.ttf') format('truetype'),
|
||||
url('../assets/fonts/social-media.svg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
```
|
||||
1. Declare css font-family:
|
||||
```
|
||||
@font-face {
|
||||
font-family: 'social-media';
|
||||
src: url('../assets/fonts/social-media.eot');
|
||||
src: url('../assets/fonts/social-media.eot') format('embedded-opentype'),
|
||||
url('../assets/fonts/social-media.woff2') format('woff2'),
|
||||
url('../assets/fonts/social-media.woff') format('woff'),
|
||||
url('../assets/fonts/social-media.ttf') format('truetype'),
|
||||
url('../assets/fonts/social-media.svg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
```
|
||||
1. Create classes for each icon using their unicode character
|
||||
```
|
||||
.sm-logo-facebook:before {
|
||||
content: '\ea02';
|
||||
}
|
||||
```
|
||||
* [How to get unicode icon values?](https://stackoverflow.com/questions/27247145/get-the-unicode-icon-value-from-a-custom-font)
|
||||
```
|
||||
.sm-logo-facebook:before {
|
||||
content: '\ea02';
|
||||
}
|
||||
```
|
||||
* [How to get unicode icon values?](https://stackoverflow.com/questions/27247145/get-the-unicode-icon-value-from-a-custom-font)
|
||||
1. Write some markup:
|
||||
```
|
||||
<a href="/">
|
||||
<i className={`sm-icon sm-logo-${text} sm-24px`} />
|
||||
Hello World
|
||||
</a>
|
||||
```
|
||||
```
|
||||
<a href="/">
|
||||
<i className={`sm-icon sm-logo-${text} sm-24px`} />
|
||||
Hello World
|
||||
</a>
|
||||
```
|
||||
|
||||
## Thanks & Support
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { withRouter, Switch, Redirect, Router, Route } from 'react-router-dom';
|
||||
import { withRouter, Switch, Redirect, HashRouter, Route, BrowserRouter } from 'react-router-dom';
|
||||
// Components
|
||||
import Contracts from 'containers/Tabs/Contracts';
|
||||
import ENS from 'containers/Tabs/ENS';
|
||||
|
@ -11,11 +11,14 @@ import Swap from 'containers/Tabs/Swap';
|
|||
import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage';
|
||||
import BroadcastTx from 'containers/Tabs/BroadcastTx';
|
||||
import ErrorScreen from 'components/ErrorScreen';
|
||||
import PageNotFound from 'components/PageNotFound';
|
||||
import LogOutPrompt from 'components/LogOutPrompt';
|
||||
import { Aux } from 'components/ui';
|
||||
import { Store } from 'redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
// TODO: fix this
|
||||
interface Props {
|
||||
store: any;
|
||||
history: any;
|
||||
store: Store<AppState>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -27,12 +30,12 @@ export default class Root extends Component<Props, State> {
|
|||
error: null
|
||||
};
|
||||
|
||||
public componentDidCatch(error) {
|
||||
public componentDidCatch(error: Error) {
|
||||
this.setState({ error });
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { store, history } = this.props;
|
||||
const { store } = this.props;
|
||||
const { error } = this.state;
|
||||
|
||||
if (error) {
|
||||
|
@ -40,24 +43,38 @@ export default class Root extends Component<Props, State> {
|
|||
}
|
||||
|
||||
// key={Math.random()} = hack for HMR from https://github.com/webpack/webpack-dev-server/issues/395
|
||||
const routes = (
|
||||
<Switch>
|
||||
<Route exact={true} path="/" component={GenerateWallet} />
|
||||
<Route path="/generate" component={GenerateWallet}>
|
||||
<Route path="keystore" component={GenerateWallet} />
|
||||
<Route path="mnemonic" component={GenerateWallet} />
|
||||
</Route>
|
||||
<Route path="/help" component={Help} />
|
||||
<Route path="/swap" component={Swap} />
|
||||
<Route path="/account" component={SendTransaction}>
|
||||
<Route path="send" component={SendTransaction} />
|
||||
<Route path="info" component={SendTransaction} />
|
||||
</Route>
|
||||
<Route path="/send-transaction" component={SendTransaction} />
|
||||
<Route path="/contracts" component={Contracts} />
|
||||
<Route path="/ens" component={ENS} />
|
||||
<Route path="/sign-and-verify-message" component={SignAndVerifyMessage} />
|
||||
<Route path="/pushTx" component={BroadcastTx} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
const Router = process.env.BUILD_DOWNLOADABLE ? HashRouter : BrowserRouter;
|
||||
|
||||
return (
|
||||
<Provider store={store} key={Math.random()}>
|
||||
<Router history={history} key={Math.random()}>
|
||||
<div>
|
||||
<Route exact={true} path="/" component={GenerateWallet} />
|
||||
<Route path="/help" component={Help} />
|
||||
<Route path="/swap" component={Swap} />
|
||||
<Route path="/account" component={SendTransaction}>
|
||||
<Route path="send" component={SendTransaction} />
|
||||
<Route path="info" component={SendTransaction} />
|
||||
</Route>
|
||||
<Route path="/send-transaction" component={SendTransaction} />
|
||||
<Route path="/contracts" component={Contracts} />
|
||||
<Route path="/ens" component={ENS} />
|
||||
<Route path="/sign-and-verify-message" component={SignAndVerifyMessage} />
|
||||
<Route path="/pushTx" component={BroadcastTx} />
|
||||
<Router key={Math.random()}>
|
||||
<Aux>
|
||||
{routes}
|
||||
<LegacyRoutes />
|
||||
</div>
|
||||
<LogOutPrompt />
|
||||
</Aux>
|
||||
</Router>
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import { generate } from 'ethereumjs-wallet';
|
||||
import * as interfaces from './actionTypes';
|
||||
import { TypeKeys } from './constants';
|
||||
|
||||
export type TGenerateNewWallet = typeof generateNewWallet;
|
||||
export function generateNewWallet(password: string): interfaces.GenerateNewWalletAction {
|
||||
return {
|
||||
type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET,
|
||||
wallet: generate(),
|
||||
password
|
||||
};
|
||||
}
|
||||
|
||||
export type TContinueToPaper = typeof continueToPaper;
|
||||
export function continueToPaper(): interfaces.ContinueToPaperAction {
|
||||
return { type: TypeKeys.GENERATE_WALLET_CONTINUE_TO_PAPER };
|
||||
}
|
||||
|
||||
export type TResetGenerateWallet = typeof resetGenerateWallet;
|
||||
export function resetGenerateWallet(): interfaces.ResetGenerateWalletAction {
|
||||
return { type: TypeKeys.GENERATE_WALLET_RESET };
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import { IFullWallet } from 'ethereumjs-wallet';
|
||||
import { TypeKeys } from './constants';
|
||||
|
||||
/*** Generate Wallet File ***/
|
||||
export interface GenerateNewWalletAction {
|
||||
type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET;
|
||||
wallet: IFullWallet;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/*** Reset Generate Wallet ***/
|
||||
export interface ResetGenerateWalletAction {
|
||||
type: TypeKeys.GENERATE_WALLET_RESET;
|
||||
}
|
||||
|
||||
/*** Confirm Continue To Paper ***/
|
||||
export interface ContinueToPaperAction {
|
||||
type: TypeKeys.GENERATE_WALLET_CONTINUE_TO_PAPER;
|
||||
}
|
||||
|
||||
/*** Action Union ***/
|
||||
export type GenerateWalletAction =
|
||||
| GenerateNewWalletAction
|
||||
| ContinueToPaperAction
|
||||
| ResetGenerateWalletAction;
|
|
@ -1,5 +0,0 @@
|
|||
export enum TypeKeys {
|
||||
GENERATE_WALLET_GENERATE_WALLET = 'GENERATE_WALLET_GENERATE_WALLET',
|
||||
GENERATE_WALLET_CONTINUE_TO_PAPER = 'GENERATE_WALLET_CONTINUE_TO_PAPER',
|
||||
GENERATE_WALLET_RESET = 'GENERATE_WALLET_RESET'
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export * from './constants';
|
||||
export * from './actionTypes';
|
||||
export * from './actionCreators';
|
|
@ -1,31 +1,39 @@
|
|||
import { handleJSONResponse } from 'api/utils';
|
||||
|
||||
export const rateSymbols = ['USD', 'EUR', 'GBP', 'BTC', 'CHF', 'REP', 'ETH'];
|
||||
export const rateSymbols: Symbols = ['USD', 'EUR', 'GBP', 'BTC', 'CHF', 'REP', 'ETH'];
|
||||
|
||||
export type Symbols = (keyof ISymbol)[];
|
||||
// TODO - internationalize
|
||||
const ERROR_MESSAGE = 'Could not fetch rate data.';
|
||||
const CCApi = 'https://min-api.cryptocompare.com';
|
||||
|
||||
const CCRates = (symbols: string[]) => {
|
||||
const tsyms = rateSymbols.concat(symbols).join(',');
|
||||
const tsyms = rateSymbols.concat(symbols as any).join(',');
|
||||
return `${CCApi}/data/price?fsym=ETH&tsyms=${tsyms}`;
|
||||
};
|
||||
|
||||
export interface CCResponse {
|
||||
[symbol: string]: {
|
||||
USD: number;
|
||||
EUR: number;
|
||||
GBP: number;
|
||||
BTC: number;
|
||||
CHF: number;
|
||||
REP: number;
|
||||
ETH: number;
|
||||
};
|
||||
[symbol: string]: ISymbol;
|
||||
}
|
||||
|
||||
interface ISymbol {
|
||||
USD: number;
|
||||
EUR: number;
|
||||
GBP: number;
|
||||
BTC: number;
|
||||
CHF: number;
|
||||
REP: number;
|
||||
ETH: number;
|
||||
}
|
||||
|
||||
interface IRates extends ISymbol {
|
||||
Response?: 'Error';
|
||||
}
|
||||
|
||||
export const fetchRates = (symbols: string[] = []): Promise<CCResponse> =>
|
||||
fetch(CCRates(symbols))
|
||||
.then(response => handleJSONResponse(response, ERROR_MESSAGE))
|
||||
.then(rates => {
|
||||
.then((rates: IRates) => {
|
||||
// API errors come as 200s, so check the json for error
|
||||
if (rates.Response && rates.Response === 'Error') {
|
||||
throw new Error('Failed to fetch rates');
|
||||
|
@ -35,12 +43,15 @@ export const fetchRates = (symbols: string[] = []): Promise<CCResponse> =>
|
|||
// do it all in one request
|
||||
// to their respective rates via ETH.
|
||||
return symbols.reduce(
|
||||
(eqRates, sym) => {
|
||||
(eqRates, sym: keyof ISymbol) => {
|
||||
if (rates[sym]) {
|
||||
eqRates[sym] = rateSymbols.reduce((symRates, rateSym) => {
|
||||
symRates[rateSym] = 1 / rates[sym] * rates[rateSym];
|
||||
return symRates;
|
||||
}, {});
|
||||
eqRates[sym] = rateSymbols.reduce(
|
||||
(symRates, rateSym) => {
|
||||
symRates[rateSym] = 1 / rates[sym] * rates[rateSym];
|
||||
return symRates;
|
||||
},
|
||||
{} as ISymbol
|
||||
);
|
||||
}
|
||||
return eqRates;
|
||||
},
|
||||
|
@ -54,6 +65,6 @@ export const fetchRates = (symbols: string[] = []): Promise<CCResponse> =>
|
|||
REP: rates.REP,
|
||||
ETH: 1
|
||||
}
|
||||
}
|
||||
} as CCResponse
|
||||
);
|
||||
});
|
||||
|
|
|
@ -27,6 +27,16 @@ export function loadBityRatesSucceededSwap(
|
|||
};
|
||||
}
|
||||
|
||||
export type TLoadShapeshiftSucceededSwap = typeof loadShapeshiftRatesSucceededSwap;
|
||||
export function loadShapeshiftRatesSucceededSwap(
|
||||
payload
|
||||
): interfaces.LoadShapshiftRatesSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TDestinationAddressSwap = typeof destinationAddressSwap;
|
||||
export function destinationAddressSwap(payload?: string): interfaces.DestinationAddressSwapAction {
|
||||
return {
|
||||
|
@ -49,6 +59,13 @@ export function loadBityRatesRequestedSwap(): interfaces.LoadBityRatesRequestedS
|
|||
};
|
||||
}
|
||||
|
||||
export type TLoadShapeshiftRequestedSwap = typeof loadShapeshiftRatesRequestedSwap;
|
||||
export function loadShapeshiftRatesRequestedSwap(): interfaces.LoadShapeshiftRequestedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED
|
||||
};
|
||||
}
|
||||
|
||||
export type TStopLoadBityRatesSwap = typeof stopLoadBityRatesSwap;
|
||||
export function stopLoadBityRatesSwap(): interfaces.StopLoadBityRatesSwapAction {
|
||||
return {
|
||||
|
@ -56,6 +73,13 @@ export function stopLoadBityRatesSwap(): interfaces.StopLoadBityRatesSwapAction
|
|||
};
|
||||
}
|
||||
|
||||
export type TStopLoadShapeshiftRatesSwap = typeof stopLoadShapeshiftRatesSwap;
|
||||
export function stopLoadShapeshiftRatesSwap(): interfaces.StopLoadShapeshiftRatesSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_STOP_LOAD_SHAPESHIFT_RATES
|
||||
};
|
||||
}
|
||||
|
||||
export type TOrderTimeSwap = typeof orderTimeSwap;
|
||||
export function orderTimeSwap(payload: number): interfaces.OrderSwapTimeSwapAction {
|
||||
return {
|
||||
|
@ -74,6 +98,16 @@ export function bityOrderCreateSucceededSwap(
|
|||
};
|
||||
}
|
||||
|
||||
export type TShapeshiftOrderCreateSucceededSwap = typeof shapeshiftOrderCreateSucceededSwap;
|
||||
export function shapeshiftOrderCreateSucceededSwap(
|
||||
payload: interfaces.ShapeshiftOrderResponse
|
||||
): interfaces.ShapeshiftOrderCreateSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_SUCCEEDED,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TBityOrderCreateRequestedSwap = typeof bityOrderCreateRequestedSwap;
|
||||
export function bityOrderCreateRequestedSwap(
|
||||
amount: number,
|
||||
|
@ -82,7 +116,7 @@ export function bityOrderCreateRequestedSwap(
|
|||
mode: number = 0
|
||||
): interfaces.BityOrderCreateRequestedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_ORDER_CREATE_REQUESTED,
|
||||
type: TypeKeys.SWAP_BITY_ORDER_CREATE_REQUESTED,
|
||||
payload: {
|
||||
amount,
|
||||
destinationAddress,
|
||||
|
@ -92,29 +126,70 @@ export function bityOrderCreateRequestedSwap(
|
|||
};
|
||||
}
|
||||
|
||||
export function bityOrderCreateFailedSwap(): interfaces.BityOrderCreateFailedSwapAction {
|
||||
export type TShapeshiftOrderCreateRequestedSwap = typeof shapeshiftOrderCreateRequestedSwap;
|
||||
export function shapeshiftOrderCreateRequestedSwap(
|
||||
withdrawal: string,
|
||||
originKind: string,
|
||||
destinationKind: string,
|
||||
destinationAmount: number
|
||||
): interfaces.ShapeshiftOrderCreateRequestedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_ORDER_CREATE_FAILED
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED,
|
||||
payload: {
|
||||
withdrawal,
|
||||
originKind,
|
||||
destinationKind,
|
||||
destinationAmount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type TOrderStatusSucceededSwap = typeof orderStatusSucceededSwap;
|
||||
export function orderStatusSucceededSwap(
|
||||
export function bityOrderCreateFailedSwap(): interfaces.BityOrderCreateFailedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_CREATE_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
export function shapeshiftOrderCreateFailedSwap(): interfaces.ShapeshiftOrderCreateFailedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
export type TBityOrderStatusSucceededSwap = typeof bityOrderStatusSucceededSwap;
|
||||
export function bityOrderStatusSucceededSwap(
|
||||
payload: interfaces.BityOrderResponse
|
||||
): interfaces.OrderStatusSucceededSwapAction {
|
||||
): interfaces.BityOrderStatusSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_STATUS_SUCCEEDED,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TOrderStatusRequestedSwap = typeof orderStatusRequestedSwap;
|
||||
export function orderStatusRequestedSwap(): interfaces.OrderStatusRequestedSwapAction {
|
||||
export type TShapeshiftOrderStatusSucceededSwap = typeof shapeshiftOrderStatusSucceededSwap;
|
||||
export function shapeshiftOrderStatusSucceededSwap(
|
||||
payload: interfaces.ShapeshiftStatusResponse
|
||||
): interfaces.ShapeshiftOrderStatusSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_STATUS_SUCCEEDED,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TBityOrderStatusRequestedSwap = typeof bityOrderStatusRequested;
|
||||
export function bityOrderStatusRequested(): interfaces.BityOrderStatusRequestedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_STATUS_REQUESTED
|
||||
};
|
||||
}
|
||||
|
||||
export type TShapeshiftOrderStatusRequestedSwap = typeof shapeshiftOrderStatusRequested;
|
||||
export function shapeshiftOrderStatusRequested(): interfaces.ShapeshiftOrderStatusRequestedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_STATUS_REQUESTED
|
||||
};
|
||||
}
|
||||
|
||||
export type TStartOrderTimerSwap = typeof startOrderTimerSwap;
|
||||
export function startOrderTimerSwap(): interfaces.StartOrderTimerSwapAction {
|
||||
return {
|
||||
|
@ -136,9 +211,45 @@ export function startPollBityOrderStatus(): interfaces.StartPollBityOrderStatusA
|
|||
};
|
||||
}
|
||||
|
||||
export type TStartPollShapeshiftOrderStatus = typeof startPollShapeshiftOrderStatus;
|
||||
export function startPollShapeshiftOrderStatus(): interfaces.StartPollShapeshiftOrderStatusAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_START_POLL_SHAPESHIFT_ORDER_STATUS
|
||||
};
|
||||
}
|
||||
|
||||
export type TStopPollBityOrderStatus = typeof stopPollBityOrderStatus;
|
||||
export function stopPollBityOrderStatus(): interfaces.StopPollBityOrderStatusAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_STOP_POLL_BITY_ORDER_STATUS
|
||||
};
|
||||
}
|
||||
|
||||
export type TStopPollShapeshiftOrderStatus = typeof stopPollShapeshiftOrderStatus;
|
||||
export function stopPollShapeshiftOrderStatus(): interfaces.StopPollShapeshiftOrderStatusAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_STOP_POLL_SHAPESHIFT_ORDER_STATUS
|
||||
};
|
||||
}
|
||||
|
||||
export type TConfigureLiteSend = typeof configureLiteSend;
|
||||
export function configureLiteSend(): interfaces.ConfigureLiteSendAction {
|
||||
return { type: TypeKeys.SWAP_CONFIGURE_LITE_SEND };
|
||||
}
|
||||
|
||||
export type TShowLiteSend = typeof showLiteSend;
|
||||
export function showLiteSend(
|
||||
payload: interfaces.ShowLiteSendAction['payload']
|
||||
): interfaces.ShowLiteSendAction {
|
||||
return { type: TypeKeys.SWAP_SHOW_LITE_SEND, payload };
|
||||
}
|
||||
|
||||
export type TChangeSwapProvider = typeof changeSwapProvider;
|
||||
export function changeSwapProvider(
|
||||
payload: interfaces.ProviderName
|
||||
): interfaces.ChangeProviderSwapAcion {
|
||||
return {
|
||||
type: TypeKeys.SWAP_CHANGE_PROVIDER,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export interface Pairs {
|
|||
|
||||
export interface SwapInput {
|
||||
id: string;
|
||||
amount: number;
|
||||
amount: number | string;
|
||||
}
|
||||
|
||||
export interface SwapInputs {
|
||||
|
@ -24,6 +24,8 @@ export interface InitSwap {
|
|||
|
||||
export interface Option {
|
||||
id: string;
|
||||
status?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export interface ApiResponseObj {
|
||||
|
@ -41,6 +43,11 @@ export interface LoadBityRatesSucceededSwapAction {
|
|||
payload: ApiResponse;
|
||||
}
|
||||
|
||||
export interface LoadShapshiftRatesSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED;
|
||||
payload: ApiResponse;
|
||||
}
|
||||
|
||||
export interface DestinationAddressSwapAction {
|
||||
type: TypeKeys.SWAP_DESTINATION_ADDRESS;
|
||||
payload?: string;
|
||||
|
@ -55,6 +62,11 @@ export interface LoadBityRatesRequestedSwapAction {
|
|||
payload?: null;
|
||||
}
|
||||
|
||||
export interface LoadShapeshiftRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED;
|
||||
payload?: null;
|
||||
}
|
||||
|
||||
export interface ChangeStepSwapAction {
|
||||
type: TypeKeys.SWAP_STEP;
|
||||
payload: number;
|
||||
|
@ -64,13 +76,17 @@ export interface StopLoadBityRatesSwapAction {
|
|||
type: TypeKeys.SWAP_STOP_LOAD_BITY_RATES;
|
||||
}
|
||||
|
||||
export interface StopLoadShapeshiftRatesSwapAction {
|
||||
type: TypeKeys.SWAP_STOP_LOAD_SHAPESHIFT_RATES;
|
||||
}
|
||||
|
||||
export interface OrderSwapTimeSwapAction {
|
||||
type: TypeKeys.SWAP_ORDER_TIME;
|
||||
payload: number;
|
||||
}
|
||||
|
||||
export interface BityOrderCreateRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_ORDER_CREATE_REQUESTED;
|
||||
type: TypeKeys.SWAP_BITY_ORDER_CREATE_REQUESTED;
|
||||
payload: {
|
||||
amount: number;
|
||||
destinationAddress: string;
|
||||
|
@ -79,6 +95,16 @@ export interface BityOrderCreateRequestedSwapAction {
|
|||
};
|
||||
}
|
||||
|
||||
export interface ShapeshiftOrderCreateRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED;
|
||||
payload: {
|
||||
withdrawal: string;
|
||||
originKind: string;
|
||||
destinationKind: string;
|
||||
destinationAmount: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BityOrderInput {
|
||||
amount: string;
|
||||
currency: string;
|
||||
|
@ -99,6 +125,31 @@ export interface BityOrderResponse {
|
|||
status: string;
|
||||
}
|
||||
|
||||
export interface ShapeshiftOrderResponse {
|
||||
apiPubKey?: string;
|
||||
deposit: string;
|
||||
depositAmount: string;
|
||||
expiration: number;
|
||||
expirationFormatted?: string;
|
||||
inputCurrency?: string;
|
||||
maxLimit: number;
|
||||
minerFee: string;
|
||||
orderId: string;
|
||||
outputCurrency?: string;
|
||||
pair: string; // e.g. eth_bat
|
||||
provider?: ProviderName; // shapeshift
|
||||
quotedRate: string;
|
||||
withdrawal: string;
|
||||
withdrawalAmount: string;
|
||||
}
|
||||
|
||||
export interface ShapeshiftStatusResponse {
|
||||
status: string;
|
||||
address?: string;
|
||||
withdraw?: string;
|
||||
transaction: string;
|
||||
}
|
||||
|
||||
export type BityOrderPostResponse = BityOrderResponse & {
|
||||
payment_address: string;
|
||||
status: string;
|
||||
|
@ -109,23 +160,44 @@ export type BityOrderPostResponse = BityOrderResponse & {
|
|||
id: string;
|
||||
};
|
||||
|
||||
export type ProviderName = 'shapeshift' | 'bity';
|
||||
|
||||
export interface BityOrderCreateSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_CREATE_SUCCEEDED;
|
||||
payload: BityOrderPostResponse;
|
||||
}
|
||||
|
||||
export interface BityOrderCreateFailedSwapAction {
|
||||
type: TypeKeys.SWAP_ORDER_CREATE_FAILED;
|
||||
export interface ShapeshiftOrderCreateSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_SUCCEEDED;
|
||||
payload: ShapeshiftOrderResponse;
|
||||
}
|
||||
export interface OrderStatusRequestedSwapAction {
|
||||
|
||||
export interface BityOrderCreateFailedSwapAction {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_CREATE_FAILED;
|
||||
}
|
||||
|
||||
export interface ShapeshiftOrderCreateFailedSwapAction {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_FAILED;
|
||||
}
|
||||
|
||||
export interface BityOrderStatusRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_STATUS_REQUESTED;
|
||||
}
|
||||
|
||||
export interface OrderStatusSucceededSwapAction {
|
||||
export interface ShapeshiftOrderStatusRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_STATUS_REQUESTED;
|
||||
}
|
||||
|
||||
export interface BityOrderStatusSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_BITY_ORDER_STATUS_SUCCEEDED;
|
||||
payload: BityOrderResponse;
|
||||
}
|
||||
|
||||
export interface ShapeshiftOrderStatusSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_SHAPESHIFT_ORDER_STATUS_SUCCEEDED;
|
||||
payload: ShapeshiftStatusResponse;
|
||||
}
|
||||
|
||||
export interface StartOrderTimerSwapAction {
|
||||
type: TypeKeys.SWAP_ORDER_START_TIMER;
|
||||
}
|
||||
|
@ -138,22 +210,55 @@ export interface StartPollBityOrderStatusAction {
|
|||
type: TypeKeys.SWAP_START_POLL_BITY_ORDER_STATUS;
|
||||
}
|
||||
|
||||
export interface StartPollShapeshiftOrderStatusAction {
|
||||
type: TypeKeys.SWAP_START_POLL_SHAPESHIFT_ORDER_STATUS;
|
||||
}
|
||||
|
||||
export interface StopPollBityOrderStatusAction {
|
||||
type: TypeKeys.SWAP_STOP_POLL_BITY_ORDER_STATUS;
|
||||
}
|
||||
|
||||
export interface StopPollShapeshiftOrderStatusAction {
|
||||
type: TypeKeys.SWAP_STOP_POLL_SHAPESHIFT_ORDER_STATUS;
|
||||
}
|
||||
|
||||
export interface ChangeProviderSwapAcion {
|
||||
type: TypeKeys.SWAP_CHANGE_PROVIDER;
|
||||
payload: ProviderName;
|
||||
}
|
||||
|
||||
export interface ConfigureLiteSendAction {
|
||||
type: TypeKeys.SWAP_CONFIGURE_LITE_SEND;
|
||||
}
|
||||
|
||||
export interface ShowLiteSendAction {
|
||||
type: TypeKeys.SWAP_SHOW_LITE_SEND;
|
||||
payload: boolean;
|
||||
}
|
||||
|
||||
/*** Action Type Union ***/
|
||||
export type SwapAction =
|
||||
| ChangeStepSwapAction
|
||||
| InitSwap
|
||||
| LoadBityRatesSucceededSwapAction
|
||||
| LoadShapshiftRatesSucceededSwapAction
|
||||
| DestinationAddressSwapAction
|
||||
| RestartSwapAction
|
||||
| LoadBityRatesRequestedSwapAction
|
||||
| LoadShapeshiftRequestedSwapAction
|
||||
| StopLoadBityRatesSwapAction
|
||||
| StopLoadShapeshiftRatesSwapAction
|
||||
| BityOrderCreateRequestedSwapAction
|
||||
| ShapeshiftOrderCreateRequestedSwapAction
|
||||
| BityOrderCreateSucceededSwapAction
|
||||
| OrderStatusSucceededSwapAction
|
||||
| ShapeshiftOrderCreateSucceededSwapAction
|
||||
| BityOrderStatusSucceededSwapAction
|
||||
| ShapeshiftOrderStatusSucceededSwapAction
|
||||
| StartPollBityOrderStatusAction
|
||||
| StartPollShapeshiftOrderStatusAction
|
||||
| BityOrderCreateFailedSwapAction
|
||||
| OrderSwapTimeSwapAction;
|
||||
| ShapeshiftOrderCreateFailedSwapAction
|
||||
| OrderSwapTimeSwapAction
|
||||
| ChangeProviderSwapAcion
|
||||
| ConfigureLiteSendAction
|
||||
| ShowLiteSendAction;
|
||||
|
|
|
@ -2,18 +2,31 @@ export enum TypeKeys {
|
|||
SWAP_STEP = 'SWAP_STEP',
|
||||
SWAP_INIT = 'SWAP_INIT',
|
||||
SWAP_LOAD_BITY_RATES_SUCCEEDED = 'SWAP_LOAD_BITY_RATES_SUCCEEDED',
|
||||
SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED = 'SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED',
|
||||
SWAP_DESTINATION_ADDRESS = 'SWAP_DESTINATION_ADDRESS',
|
||||
SWAP_RESTART = 'SWAP_RESTART',
|
||||
SWAP_LOAD_BITY_RATES_REQUESTED = 'SWAP_LOAD_BITY_RATES_REQUESTED',
|
||||
SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED = 'SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED',
|
||||
SWAP_STOP_LOAD_BITY_RATES = 'SWAP_STOP_LOAD_BITY_RATES',
|
||||
SWAP_STOP_LOAD_SHAPESHIFT_RATES = 'SWAP_STOP_LOAD_SHAPESHIFT_RATES',
|
||||
SWAP_ORDER_TIME = 'SWAP_ORDER_TIME',
|
||||
SWAP_BITY_ORDER_CREATE_SUCCEEDED = 'SWAP_BITY_ORDER_CREATE_SUCCEEDED',
|
||||
SWAP_SHAPESHIFT_ORDER_CREATE_SUCCEEDED = 'SWAP_SHAPESHIFT_ORDER_CREATE_SUCCEEDED',
|
||||
SWAP_BITY_ORDER_STATUS_SUCCEEDED = 'SWAP_BITY_ORDER_STATUS_SUCCEEDED',
|
||||
SWAP_SHAPESHIFT_ORDER_STATUS_SUCCEEDED = 'SWAP_SHAPESHIFT_ORDER_STATUS_SUCCEEDED',
|
||||
SWAP_BITY_ORDER_STATUS_REQUESTED = 'SWAP_BITY_ORDER_STATUS_REQUESTED',
|
||||
SWAP_SHAPESHIFT_ORDER_STATUS_REQUESTED = 'SWAP_SHAPESHIFT_ORDER_STATUS_REQUESTED',
|
||||
SWAP_ORDER_START_TIMER = 'SWAP_ORDER_START_TIMER',
|
||||
SWAP_ORDER_STOP_TIMER = 'SWAP_ORDER_STOP_TIMER',
|
||||
SWAP_START_POLL_BITY_ORDER_STATUS = 'SWAP_START_POLL_BITY_ORDER_STATUS',
|
||||
SWAP_START_POLL_SHAPESHIFT_ORDER_STATUS = 'SWAP_START_POLL_SHAPESHIFT_ORDER_STATUS',
|
||||
SWAP_STOP_POLL_BITY_ORDER_STATUS = 'SWAP_STOP_POLL_BITY_ORDER_STATUS',
|
||||
SWAP_ORDER_CREATE_REQUESTED = 'SWAP_ORDER_CREATE_REQUESTED',
|
||||
SWAP_ORDER_CREATE_FAILED = 'SWAP_ORDER_CREATE_FAILED'
|
||||
SWAP_STOP_POLL_SHAPESHIFT_ORDER_STATUS = 'SWAP_STOP_POLL_SHAPESHIFT_ORDER_STATUS',
|
||||
SWAP_BITY_ORDER_CREATE_REQUESTED = 'SWAP_ORDER_CREATE_REQUESTED',
|
||||
SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED = 'SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED',
|
||||
SWAP_BITY_ORDER_CREATE_FAILED = 'SWAP_ORDER_CREATE_FAILED',
|
||||
SWAP_SHAPESHIFT_ORDER_CREATE_FAILED = 'SWAP_SHAPESHIFT_ORDER_CREATE_FAILED',
|
||||
SWAP_CHANGE_PROVIDER = 'SWAP_CHANGE_PROVIDER',
|
||||
SWAP_CONFIGURE_LITE_SEND = 'SWAP_CONFIGURE_LITE_SEND',
|
||||
SWAP_SHOW_LITE_SEND = 'SWAP_SHOW_LITE_SEND'
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
SetNonceFieldAction,
|
||||
SetValueFieldAction,
|
||||
InputGasLimitAction,
|
||||
InputGasPriceAction,
|
||||
InputDataAction,
|
||||
InputNonceAction,
|
||||
ResetAction,
|
||||
|
@ -18,6 +19,12 @@ const inputGasLimit = (payload: InputGasLimitAction['payload']) => ({
|
|||
payload
|
||||
});
|
||||
|
||||
type TInputGasPrice = typeof inputGasPrice;
|
||||
const inputGasPrice = (payload: InputGasPriceAction['payload']) => ({
|
||||
type: TypeKeys.GAS_PRICE_INPUT,
|
||||
payload
|
||||
});
|
||||
|
||||
type TInputNonce = typeof inputNonce;
|
||||
const inputNonce = (payload: InputNonceAction['payload']) => ({
|
||||
type: TypeKeys.NONCE_INPUT,
|
||||
|
@ -71,6 +78,7 @@ const reset = (): ResetAction => ({ type: TypeKeys.RESET });
|
|||
|
||||
export {
|
||||
TInputGasLimit,
|
||||
TInputGasPrice,
|
||||
TInputNonce,
|
||||
TInputData,
|
||||
TSetGasLimitField,
|
||||
|
@ -81,6 +89,7 @@ export {
|
|||
TSetGasPriceField,
|
||||
TReset,
|
||||
inputGasLimit,
|
||||
inputGasPrice,
|
||||
inputNonce,
|
||||
inputData,
|
||||
setGasLimitField,
|
||||
|
|
|
@ -6,6 +6,10 @@ interface InputGasLimitAction {
|
|||
type: TypeKeys.GAS_LIMIT_INPUT;
|
||||
payload: string;
|
||||
}
|
||||
interface InputGasPriceAction {
|
||||
type: TypeKeys.GAS_PRICE_INPUT;
|
||||
payload: string;
|
||||
}
|
||||
interface InputDataAction {
|
||||
type: TypeKeys.DATA_FIELD_INPUT;
|
||||
payload: string;
|
||||
|
@ -48,6 +52,7 @@ interface SetToFieldAction {
|
|||
payload: {
|
||||
raw: string;
|
||||
value: Address | null;
|
||||
error?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -79,6 +84,7 @@ type FieldAction =
|
|||
|
||||
export {
|
||||
InputGasLimitAction,
|
||||
InputGasPriceAction,
|
||||
InputDataAction,
|
||||
InputNonceAction,
|
||||
SetGasLimitFieldAction,
|
||||
|
|
|
@ -7,6 +7,7 @@ interface SetTokenToMetaAction {
|
|||
payload: {
|
||||
raw: string;
|
||||
value: Address | null;
|
||||
error?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ export enum TypeKeys {
|
|||
|
||||
DATA_FIELD_INPUT = 'DATA_FIELD_INPUT',
|
||||
GAS_LIMIT_INPUT = 'GAS_LIMIT_INPUT',
|
||||
GAS_PRICE_INPUT = 'GAS_PRICE_INPUT',
|
||||
NONCE_INPUT = 'NONCE_INPUT',
|
||||
|
||||
DATA_FIELD_SET = 'DATA_FIELD_SET',
|
||||
|
|
|
@ -88,6 +88,34 @@ export function setTokenBalancesRejected(): types.SetTokenBalancesRejectedAction
|
|||
};
|
||||
}
|
||||
|
||||
export function setTokenBalancePending(
|
||||
payload: types.SetTokenBalancePendingAction['payload']
|
||||
): types.SetTokenBalancePendingAction {
|
||||
return {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCE_PENDING,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TSetTokenBalanceFulfilled = typeof setTokenBalanceFulfilled;
|
||||
export function setTokenBalanceFulfilled(payload: {
|
||||
[key: string]: {
|
||||
balance: TokenValue;
|
||||
error: string | null;
|
||||
};
|
||||
}): types.SetTokenBalanceFulfilledAction {
|
||||
return {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCE_FULFILLED,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export function setTokenBalanceRejected(): types.SetTokenBalanceRejectedAction {
|
||||
return {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCE_REJECTED
|
||||
};
|
||||
}
|
||||
|
||||
export type TScanWalletForTokens = typeof scanWalletForTokens;
|
||||
export function scanWalletForTokens(wallet: IWallet): types.ScanWalletForTokensAction {
|
||||
return {
|
||||
|
|
|
@ -63,6 +63,25 @@ export interface SetTokenBalancesRejectedAction {
|
|||
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_REJECTED;
|
||||
}
|
||||
|
||||
export interface SetTokenBalancePendingAction {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCE_PENDING;
|
||||
payload: { tokenSymbol: string };
|
||||
}
|
||||
|
||||
export interface SetTokenBalanceFulfilledAction {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCE_FULFILLED;
|
||||
payload: {
|
||||
[key: string]: {
|
||||
balance: TokenValue;
|
||||
error: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetTokenBalanceRejectedAction {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCE_REJECTED;
|
||||
}
|
||||
|
||||
export interface ScanWalletForTokensAction {
|
||||
type: TypeKeys.WALLET_SCAN_WALLET_FOR_TOKENS;
|
||||
payload: IWallet;
|
||||
|
@ -108,6 +127,9 @@ export type WalletAction =
|
|||
| SetTokenBalancesPendingAction
|
||||
| SetTokenBalancesFulfilledAction
|
||||
| SetTokenBalancesRejectedAction
|
||||
| SetTokenBalancePendingAction
|
||||
| SetTokenBalanceFulfilledAction
|
||||
| SetTokenBalanceRejectedAction
|
||||
| ScanWalletForTokensAction
|
||||
| SetWalletTokensAction
|
||||
| SetWalletConfigAction;
|
||||
|
|
|
@ -10,6 +10,9 @@ export enum TypeKeys {
|
|||
WALLET_SET_TOKEN_BALANCES_PENDING = 'WALLET_SET_TOKEN_BALANCES_PENDING',
|
||||
WALLET_SET_TOKEN_BALANCES_FULFILLED = 'WALLET_SET_TOKEN_BALANCES_FULFILLED',
|
||||
WALLET_SET_TOKEN_BALANCES_REJECTED = 'WALLET_SET_TOKEN_BALANCES_REJECTED',
|
||||
WALLET_SET_TOKEN_BALANCE_PENDING = 'WALLET_SET_TOKEN_BALANCE_PENDING',
|
||||
WALLET_SET_TOKEN_BALANCE_FULFILLED = 'WALLET_SET_TOKEN_BALANCE_FULFILLED',
|
||||
WALLET_SET_TOKEN_BALANCE_REJECTED = 'WALLET_SET_TOKEN_BALANCE_REJECTED',
|
||||
WALLET_SCAN_WALLET_FOR_TOKENS = 'WALLET_SCAN_WALLET_FOR_TOKENS',
|
||||
WALLET_SET_WALLET_TOKENS = 'WALLET_SET_WALLET_TOKENS',
|
||||
WALLET_SET_CONFIG = 'WALLET_SET_CONFIG',
|
||||
|
|
|
@ -1,10 +1,34 @@
|
|||
import bityConfig, { WhitelistedCoins } from 'config/bity';
|
||||
import { checkHttpStatus, parseJSON, filter } from './utils';
|
||||
import bitcoinIcon from 'assets/images/bitcoin.png';
|
||||
import repIcon from 'assets/images/augur.png';
|
||||
import etherIcon from 'assets/images/ether.png';
|
||||
|
||||
const isCryptoPair = (from: string, to: string, arr: WhitelistedCoins[]) => {
|
||||
return filter(from, arr) && filter(to, arr);
|
||||
};
|
||||
|
||||
const btcOptions = {
|
||||
id: 'BTC',
|
||||
status: 'available',
|
||||
image: bitcoinIcon,
|
||||
name: 'Bitcoin'
|
||||
};
|
||||
|
||||
const ethOptions = {
|
||||
id: 'ETH',
|
||||
status: 'available',
|
||||
image: etherIcon,
|
||||
name: 'Ether'
|
||||
};
|
||||
|
||||
const repOptions = {
|
||||
id: 'REP',
|
||||
status: 'available',
|
||||
image: repIcon,
|
||||
name: 'Augur'
|
||||
};
|
||||
|
||||
export function getAllRates() {
|
||||
const mappedRates = {};
|
||||
return _getAllRates().then(bityRates => {
|
||||
|
@ -14,9 +38,31 @@ export function getAllRates() {
|
|||
const to = { id: pairName.substring(3, 6) };
|
||||
// Check if rate exists= && check if the pair only crypto to crypto, not crypto to fiat, or any other combination
|
||||
if (parseFloat(each.rate_we_sell) && isCryptoPair(from.id, to.id, ['BTC', 'ETH', 'REP'])) {
|
||||
let fromOptions;
|
||||
let toOptions;
|
||||
switch (from.id) {
|
||||
case 'BTC':
|
||||
fromOptions = btcOptions;
|
||||
break;
|
||||
case 'ETH':
|
||||
fromOptions = ethOptions;
|
||||
break;
|
||||
case 'REP':
|
||||
fromOptions = repOptions;
|
||||
}
|
||||
switch (to.id) {
|
||||
case 'BTC':
|
||||
toOptions = btcOptions;
|
||||
break;
|
||||
case 'ETH':
|
||||
toOptions = ethOptions;
|
||||
break;
|
||||
case 'REP':
|
||||
toOptions = repOptions;
|
||||
}
|
||||
mappedRates[pairName] = {
|
||||
id: pairName,
|
||||
options: [from, to],
|
||||
options: [fromOptions, toOptions],
|
||||
rate: parseFloat(each.rate_we_sell)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
import { checkHttpStatus, parseJSON } from 'api/utils';
|
||||
|
||||
const SHAPESHIFT_BASE_URL = 'https://shapeshift.io';
|
||||
|
||||
export const SHAPESHIFT_TOKEN_WHITELIST = [
|
||||
'OMG',
|
||||
'REP',
|
||||
'SNT',
|
||||
'SNGLS',
|
||||
'ZRX',
|
||||
'SWT',
|
||||
'ANT',
|
||||
'BAT',
|
||||
'BNT',
|
||||
'CVC',
|
||||
'DNT',
|
||||
'1ST',
|
||||
'GNO',
|
||||
'GNT',
|
||||
'EDG',
|
||||
'FUN',
|
||||
'RLC',
|
||||
'TRST',
|
||||
'GUP',
|
||||
'ETH'
|
||||
];
|
||||
export const SHAPESHIFT_WHITELIST = [...SHAPESHIFT_TOKEN_WHITELIST, 'ETC', 'BTC'];
|
||||
|
||||
class ShapeshiftService {
|
||||
public whitelist = SHAPESHIFT_WHITELIST;
|
||||
private url = SHAPESHIFT_BASE_URL;
|
||||
private apiKey = '0ca1ccd50b708a3f8c02327f0caeeece06d3ddc1b0ac749a987b453ee0f4a29bdb5da2e53bc35e57fb4bb7ae1f43c93bb098c3c4716375fc1001c55d8c94c160';
|
||||
private postHeaders = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
public checkStatus(address) {
|
||||
return fetch(`${this.url}/txStat/${address}`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
public sendAmount(withdrawal, originKind, destinationKind, destinationAmount) {
|
||||
const pair = `${originKind.toLowerCase()}_${destinationKind.toLowerCase()}`;
|
||||
|
||||
return fetch(`${this.url}/sendamount`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
amount: destinationAmount,
|
||||
pair,
|
||||
apiKey: this.apiKey,
|
||||
withdrawal
|
||||
}),
|
||||
headers: new Headers(this.postHeaders)
|
||||
})
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
public getCoins() {
|
||||
return fetch(`${this.url}/getcoins`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
public getAllRates = async () => {
|
||||
const marketInfo = await this.getMarketInfo();
|
||||
const pairRates = await this.getPairRates(marketInfo);
|
||||
const checkAvl = await this.checkAvl(pairRates);
|
||||
const mappedRates = this.mapMarketInfo(checkAvl);
|
||||
return mappedRates;
|
||||
};
|
||||
|
||||
private getPairRates(marketInfo) {
|
||||
const filteredMarketInfo = marketInfo.filter(obj => {
|
||||
const { pair } = obj;
|
||||
const pairArr = pair.split('_');
|
||||
return this.whitelist.includes(pairArr[0]) && this.whitelist.includes(pairArr[1])
|
||||
? true
|
||||
: false;
|
||||
});
|
||||
const pairRates = filteredMarketInfo.map(p => {
|
||||
const { pair } = p;
|
||||
const singlePair = Promise.resolve(this.getSinglePairRate(pair));
|
||||
return { ...p, ...singlePair };
|
||||
});
|
||||
return pairRates;
|
||||
}
|
||||
|
||||
private async checkAvl(pairRates) {
|
||||
const avlCoins = await this.getAvlCoins();
|
||||
const mapAvl = pairRates.map(p => {
|
||||
const { pair } = p;
|
||||
const pairArr = pair.split('_');
|
||||
|
||||
if (pairArr[0] in avlCoins && pairArr[1] in avlCoins) {
|
||||
return {
|
||||
...p,
|
||||
...{
|
||||
[pairArr[0]]: {
|
||||
name: avlCoins[pairArr[0]].name,
|
||||
status: avlCoins[pairArr[0]].status,
|
||||
image: avlCoins[pairArr[0]].image
|
||||
},
|
||||
[pairArr[1]]: {
|
||||
name: avlCoins[pairArr[1]].name,
|
||||
status: avlCoins[pairArr[1]].status,
|
||||
image: avlCoins[pairArr[1]].image
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return mapAvl;
|
||||
}
|
||||
|
||||
private getAvlCoins() {
|
||||
return fetch(`${this.url}/getcoins`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
private getSinglePairRate(pair) {
|
||||
return fetch(`${this.url}/rate/${pair}`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
private getMarketInfo() {
|
||||
return fetch(`${this.url}/marketinfo`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
private isWhitelisted(coin) {
|
||||
return this.whitelist.includes(coin);
|
||||
}
|
||||
|
||||
private mapMarketInfo(marketInfo) {
|
||||
const tokenMap = {};
|
||||
marketInfo.forEach(m => {
|
||||
const originKind = m.pair.substring(0, 3);
|
||||
const destinationKind = m.pair.substring(4, 7);
|
||||
if (this.isWhitelisted(originKind) && this.isWhitelisted(destinationKind)) {
|
||||
const pairName = originKind + destinationKind;
|
||||
const { rate, limit, min } = m;
|
||||
tokenMap[pairName] = {
|
||||
id: pairName,
|
||||
options: [
|
||||
{
|
||||
id: originKind,
|
||||
status: m[originKind].status,
|
||||
image: m[originKind].image,
|
||||
name: m[originKind].name
|
||||
},
|
||||
{
|
||||
id: destinationKind,
|
||||
status: m[destinationKind].status,
|
||||
image: m[destinationKind].image,
|
||||
name: m[destinationKind].name
|
||||
}
|
||||
],
|
||||
rate,
|
||||
limit,
|
||||
min
|
||||
};
|
||||
}
|
||||
});
|
||||
return tokenMap;
|
||||
}
|
||||
}
|
||||
|
||||
const shapeshift = new ShapeshiftService();
|
||||
|
||||
export default shapeshift;
|
|
@ -1,10 +1,10 @@
|
|||
import { indexOf } from 'lodash';
|
||||
import indexOf from 'lodash/indexOf';
|
||||
|
||||
export const filter = (i: any, arr: any[]) => {
|
||||
return -1 !== indexOf(arr, i) ? true : false;
|
||||
};
|
||||
|
||||
export function checkHttpStatus(response) {
|
||||
export function checkHttpStatus(response: Response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
} else {
|
||||
|
@ -12,11 +12,11 @@ export function checkHttpStatus(response) {
|
|||
}
|
||||
}
|
||||
|
||||
export function parseJSON(response) {
|
||||
export function parseJSON(response: Response) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function handleJSONResponse(response, errorMessage) {
|
||||
export async function handleJSONResponse(response: Response, errorMessage: string) {
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,147 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1050 350" style="enable-background:new 0 0 1050 350;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#273C51;}
|
||||
.st2{fill:url(#SVGID_1_);}
|
||||
.st3{fill:#466284;}
|
||||
.st4{fill:#354D6A;}
|
||||
.st5{fill:url(#SVGID_2_);}
|
||||
.st6{fill:url(#SVGID_3_);}
|
||||
.st7{fill:url(#SVGID_4_);}
|
||||
.st8{fill:url(#SVGID_5_);}
|
||||
.st9{fill:url(#SVGID_6_);}
|
||||
.st10{fill:url(#SVGID_7_);}
|
||||
.st11{fill:url(#SVGID_8_);}
|
||||
.st12{fill:url(#SVGID_9_);}
|
||||
.st13{fill:url(#SVGID_10_);}
|
||||
.st14{fill:none;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M280.6,198.7c-15.2,0-31.5,6-31.5,20.6c0,13,14.9,16.8,32.6,19.7c24,3.8,47.6,8.6,47.6,35.6
|
||||
c-0.2,26.9-25.9,35.6-48.8,35.6c-21.2,0-41.5-7.7-50.7-27.8l12.3-7.2c7.7,14.2,23.8,21.1,38.6,21.1c14.6,0,33.9-4.6,33.9-22.3
|
||||
c0.2-14.9-16.6-19.2-34.6-21.9c-23.1-3.6-45.6-8.9-45.6-33.2c-0.3-25,25.2-33.6,45.9-33.6c17.8,0,34.8,3.6,45.4,21.8l-11.3,7
|
||||
C307.9,203.7,294,198.9,280.6,198.7z"/>
|
||||
<path class="st0" d="M353.8,188.1v49.2c7.2-11.1,18.5-14.9,29.3-15.1c23.8,0,35.5,15.8,35.5,39.1v46.6h-13.9v-46.4
|
||||
c0-16.6-8.6-26-24-26S354,247.5,354,263v44.9h-14V187.9h13.9V188.1z"/>
|
||||
<path class="st0" d="M504.4,308.2l-0.3-15.4c-6.7,11.7-19.5,17.1-31.2,17.1c-24.3,0-43.3-16.8-43.3-44.4
|
||||
c0-27.4,19.4-43.9,43.5-43.7c12.7,0,25.2,5.8,31.4,16.8l0.2-15.4h13.7v84.6h-13.5L504.4,308.2z M473.5,235.2
|
||||
c-16.8,0-30.3,12-30.3,30.8s13.5,31,30.3,31c40.8,0,40.8-62,0.2-62L473.5,235.2z"/>
|
||||
<path class="st0" d="M529.5,223.6h13.4l0.7,16.3c6.7-11.3,19.2-17.8,32.6-17.8c24.3,0.5,42.1,17.6,42.1,43.7
|
||||
c0,26.7-17.6,44-43,44c-12,0-25.4-5.1-32-17.1v55.2h-13.7V223.6z M604.2,266c0-19-12.5-30.3-29.8-30.3c-17.6,0-29.6,13-29.6,30.3
|
||||
s12.5,30.3,29.6,30.5C591.3,296.5,604.2,285.1,604.2,266z"/>
|
||||
<path class="st0" d="M708.9,294.3c-8.6,10.1-23.3,15.1-36.5,15.1c-26.2,0-44.5-17.3-44.5-44.2c0-25.5,18.3-43.9,43.9-43.9
|
||||
c25.9,0,45.6,15.9,42.3,49.7h-72c1.5,15.6,14.4,25.4,30.7,25.4c9.6,0,21.2-3.8,26.9-10.6l9.4,8.6H708.9z M700.6,259.4
|
||||
c-0.7-16.4-12-25.4-28.6-25.4c-14.7,0-27.6,8.9-30,25.2h58.6V259.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M771.2,198.7c-15.2,0-31.5,6-31.5,20.6c0,13,14.9,16.8,32.6,19.7c24,3.8,47.6,8.6,47.6,35.6
|
||||
c-0.2,26.9-25.9,35.6-48.8,35.6c-21.2,0-41.5-7.7-50.7-27.8l12.3-7.2c7.7,14.2,23.8,21.1,38.6,21.1c14.6,0,33.9-4.6,33.9-22.3
|
||||
c0.2-14.9-16.6-19.2-34.6-21.9c-23.1-3.6-45.6-8.9-45.6-33.2c-0.3-25,25.2-33.6,45.9-33.6c17.8,0,34.8,3.6,45.4,21.8l-11.3,7
|
||||
C798.5,203.7,784.6,198.9,771.2,198.7z"/>
|
||||
<path class="st0" d="M844.4,188.1v49.2c7.2-11.1,18.5-14.9,29.3-15.1c23.8,0,35.5,15.8,35.5,39.1v46.6h-13.9v-46.4
|
||||
c0-16.6-8.6-26-24-26s-26.7,12.2-26.7,27.6v44.9h-14V187.9h13.9V188.1z"/>
|
||||
<path class="st0" d="M920.6,307.8h14v-83.4h-14V307.8z M927.8,211.7l-11.1-12.2l11.1-12.2l11.1,12.2L927.8,211.7z"/>
|
||||
<g>
|
||||
<polygon class="st0" points="960.8,308 960.8,307.8 960.7,307.8 "/>
|
||||
<path class="st0" d="M974.7,217.6c0-12.7,5.8-18.3,14.9-18.3c0.2,0,0.4,0,0.5,0l3.2-12.1c-1.3-0.2-2.7-0.3-4-0.3
|
||||
c-17.8,0-28.4,11.3-28.4,30.7v6.7h-16.6v12.3h16.6v71.3h13.9v-71.3h16.8v-12.3h-16.8V217.6z"/>
|
||||
</g>
|
||||
<path class="st0" d="M1046.1,296.1c-1.1,0.2-2.2,0.3-3.3,0.3c-10.1,0-13.4-6.3-13.4-16.3v-43.6h18v-12.2h-17.9v-25.8l-14,1.5
|
||||
v24.2h-17v12.2h17v43.6c0,18.7,8.6,29.3,26.7,29c2.2-0.1,4.4-0.3,6.5-0.8L1046.1,296.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<polygon class="st1" points="216,82.1 230.5,-0.7 169.9,24.6 103.3,24.6 42.7,-0.8 57.3,82.1 43.6,128.3 56.5,136.4 0,186 0,232.6
|
||||
64.6,321.6 125.5,342 125.6,342.1 173.2,317.7 173.3,317.6 173.3,281.5 146.3,266.7 146.3,266.7 146.3,266.7 188.7,153.8
|
||||
188.4,153.8 229.6,128.3 "/>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="136.7364" y1="25.9921" x2="60.3198" y2="247.6647">
|
||||
<stop offset="0.1345" style="stop-color:#2B415B"/>
|
||||
<stop offset="0.3762" style="stop-color:#3B5676"/>
|
||||
<stop offset="0.6923" style="stop-color:#54769E"/>
|
||||
<stop offset="0.7901" style="stop-color:#52749B"/>
|
||||
<stop offset="0.8614" style="stop-color:#4D6C92"/>
|
||||
<stop offset="0.9244" style="stop-color:#436082"/>
|
||||
<stop offset="0.9822" style="stop-color:#364F6C"/>
|
||||
<stop offset="1" style="stop-color:#314863"/>
|
||||
</linearGradient>
|
||||
<polygon class="st2" points="97.7,100.3 0,186 136.1,264.3 136.6,102.9 "/>
|
||||
<polygon class="st3" points="83.8,153.3 136.2,293.4 136.6,161.1 "/>
|
||||
<polygon class="st4" points="188.7,153.8 136.2,293.4 136.6,161.1 "/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="230.1033" y1="127.4219" x2="34.0475" y2="14.229">
|
||||
<stop offset="0" style="stop-color:#54769E"/>
|
||||
<stop offset="0.4802" style="stop-color:#53749C"/>
|
||||
<stop offset="0.6878" style="stop-color:#4F6F95"/>
|
||||
<stop offset="0.8423" style="stop-color:#486588"/>
|
||||
<stop offset="0.9095" style="stop-color:#435F80"/>
|
||||
</linearGradient>
|
||||
<polygon class="st5" points="230.5,-0.7 178.4,26.7 136.7,35.8 94.6,26.7 42.7,-0.8 60.6,81.9 43.6,128.3 103.2,165.3
|
||||
136.3,201.3 136.3,201.6 136.5,201.4 136.7,201.6 136.7,201.3 169.8,165.3 229.6,128.3 212.6,82 "/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="342.5284" y1="63.8296" x2="150.4648" y2="63.8296">
|
||||
<stop offset="0.2539" style="stop-color:#20344C"/>
|
||||
<stop offset="0.4072" style="stop-color:#273D57"/>
|
||||
<stop offset="0.6733" style="stop-color:#395373"/>
|
||||
<stop offset="1" style="stop-color:#54769E"/>
|
||||
</linearGradient>
|
||||
<polygon class="st6" points="230.5,-0.7 216,82.1 229.6,128.3 212.6,82 "/>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-74.3281" y1="63.7777" x2="124.2335" y2="63.7777">
|
||||
<stop offset="0.2539" style="stop-color:#54769E"/>
|
||||
<stop offset="0.4133" style="stop-color:#4D6E93"/>
|
||||
<stop offset="0.6897" style="stop-color:#3C5777"/>
|
||||
<stop offset="1" style="stop-color:#233850"/>
|
||||
</linearGradient>
|
||||
<polygon class="st7" points="42.7,-0.8 57.3,82.1 43.6,128.3 60.6,81.9 "/>
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="138.4299" y1="-77.4169" x2="134.5027" y2="85.5632">
|
||||
<stop offset="6.545247e-03" style="stop-color:#54769E"/>
|
||||
<stop offset="0.1993" style="stop-color:#507198"/>
|
||||
<stop offset="0.4502" style="stop-color:#466488"/>
|
||||
<stop offset="0.7318" style="stop-color:#354F6D"/>
|
||||
<stop offset="1" style="stop-color:#21354D"/>
|
||||
</linearGradient>
|
||||
<polygon class="st8" points="42.7,-0.8 103.3,24.6 169.9,24.6 230.5,-0.7 178.4,26.7 136.7,35.8 94.6,26.7 "/>
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="173.2798" y1="-23.2345" x2="12.7505" y2="132.5687">
|
||||
<stop offset="0.2539" style="stop-color:#54769E"/>
|
||||
<stop offset="0.4102" style="stop-color:#4D6E93"/>
|
||||
<stop offset="0.6813" style="stop-color:#3C5777"/>
|
||||
<stop offset="1" style="stop-color:#22364E"/>
|
||||
</linearGradient>
|
||||
<polygon class="st9" points="60.6,81.9 57.6,90.2 120.5,32.2 "/>
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="114.997" y1="-2.4443" x2="248.7759" y2="116.8474">
|
||||
<stop offset="0.2539" style="stop-color:#54769E"/>
|
||||
<stop offset="0.4102" style="stop-color:#4D6E93"/>
|
||||
<stop offset="0.6813" style="stop-color:#3C5777"/>
|
||||
<stop offset="1" style="stop-color:#22364E"/>
|
||||
</linearGradient>
|
||||
<polygon class="st10" points="212.6,82 153,32.2 215.5,89.8 "/>
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="-31.9333" y1="230.8414" x2="255.118" y2="333.2895">
|
||||
<stop offset="0.2664" style="stop-color:#54769E"/>
|
||||
<stop offset="1" style="stop-color:#425E7F"/>
|
||||
</linearGradient>
|
||||
<polygon class="st11" points="0,186 146.3,266.7 164.8,313 125.6,327.9 64.6,321.6 0,232.6 "/>
|
||||
<polygon class="st0" points="121.1,252.8 64.8,321.4 64.6,321.6 125.5,342 125.6,342.1 173.2,317.7 173.3,317.6 173.3,281.5 "/>
|
||||
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="97.761" y1="-67.9411" x2="268.6103" y2="84.2801">
|
||||
<stop offset="0.4609" style="stop-color:#54769E;stop-opacity:0"/>
|
||||
<stop offset="0.5699" style="stop-color:#52739A;stop-opacity:0.2156"/>
|
||||
<stop offset="0.6764" style="stop-color:#4A698E;stop-opacity:0.4266"/>
|
||||
<stop offset="0.782" style="stop-color:#3D597B;stop-opacity:0.6356"/>
|
||||
<stop offset="0.8863" style="stop-color:#2C435F;stop-opacity:0.8422"/>
|
||||
<stop offset="0.9661" style="stop-color:#1B2E45"/>
|
||||
</linearGradient>
|
||||
<polygon class="st12" points="212.6,82 230.5,-0.7 178.4,26.7 153,32.2 "/>
|
||||
<polygon class="st0" points="136.6,201.6 120.1,183.5 136.6,165.3 153.2,183.5 "/>
|
||||
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="136.6099" y1="347.9733" x2="136.6099" y2="-96.2296">
|
||||
<stop offset="0.2539" style="stop-color:#54769E"/>
|
||||
<stop offset="0.4102" style="stop-color:#4D6E93"/>
|
||||
<stop offset="0.6813" style="stop-color:#3C5777"/>
|
||||
<stop offset="1" style="stop-color:#22364E"/>
|
||||
</linearGradient>
|
||||
<polygon class="st13" points="135,35.4 136.7,35.8 138.2,35.5 136.6,141 "/>
|
||||
<path class="st14" d="M77.6,35.5"/>
|
||||
<path class="st14" d="M75.1,35.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 7.8 KiB |
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="620px" height="620px" viewBox="0 0 620 620" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>swap</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<circle id="path-1" cx="170" cy="170" r="170"></circle>
|
||||
<circle id="path-3" cx="170" cy="170" r="170"></circle>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="swap">
|
||||
<g id="Yellow-Coin" transform="translate(0.000000, 280.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<circle stroke="#0E97C0" stroke-width="20" cx="170" cy="170" r="160"></circle>
|
||||
<rect id="Rectangle" fill="#FFE14D" mask="url(#mask-2)" x="-1.13333333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<rect id="Rectangle" fill="#FFCC33" mask="url(#mask-2)" x="171.133333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<circle id="Oval-2" stroke="#333333" stroke-width="20" mask="url(#mask-2)" cx="170" cy="170" r="160"></circle>
|
||||
<circle id="Oval-3" stroke="#F28618" stroke-width="20" mask="url(#mask-2)" cx="170" cy="170" r="96.3333333"></circle>
|
||||
</g>
|
||||
<g id="Blue-Coin" transform="translate(280.000000, 0.000000)">
|
||||
<mask id="mask-4" fill="white">
|
||||
<use xlink:href="#path-3"></use>
|
||||
</mask>
|
||||
<circle stroke="#0E97C0" stroke-width="20" cx="170" cy="170" r="160"></circle>
|
||||
<rect id="Rectangle" fill="#6EA6E8" mask="url(#mask-4)" x="-1.13333333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<rect id="Rectangle" fill="#5C9BE4" mask="url(#mask-4)" x="171.133333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<circle id="Oval-2" stroke="#333333" stroke-width="20" mask="url(#mask-4)" cx="170" cy="170" r="160"></circle>
|
||||
<circle id="Oval-3" stroke="#2F79CF" stroke-width="20" mask="url(#mask-4)" cx="170" cy="170" r="96.3333333"></circle>
|
||||
</g>
|
||||
<g id="Group" transform="translate(226.000000, 320.000000)" fill-rule="nonzero">
|
||||
<polygon id="Shape" fill="#0492BE" points="311.67 8 385.42 8 385.42 171.31 144.71 171.31 144.71 234.53 13 134.44 144.71 34.34 144.71 97.56 311.67 97.56"></polygon>
|
||||
<polygon id="Shape" fill="#103957" opacity="0.2" points="348.54 134.44 13 134.44 144.71 234.53 144.71 171.31 385.42 171.31 385.42 8 348.54 8"></polygon>
|
||||
<path d="M152.66,250.36 L0,134.36 L152.66,18.36 L152.66,89.61 L303.82,89.61 L303.82,0 L393.38,0 L393.38,179.11 L152.66,179.11 L152.66,250.36 Z M26.11,134.36 L136.85,218.52 L136.85,163.31 L377.58,163.31 L377.58,15.8 L319.63,15.8 L319.63,105.36 L136.86,105.36 L136.86,50.17 L26.11,134.36 Z" id="Shape" fill="#000000"></path>
|
||||
</g>
|
||||
<g id="Group" transform="translate(197.000000, 174.500000) rotate(180.000000) translate(-197.000000, -174.500000) translate(0.000000, 49.000000)" fill-rule="nonzero">
|
||||
<polygon id="Shape" fill="#0492BE" points="311.67 8 385.42 8 385.42 171.31 144.71 171.31 144.71 234.53 13 134.44 144.71 34.34 144.71 97.56 311.67 97.56"></polygon>
|
||||
<polygon id="Shape" fill="#103957" opacity="0.2" points="348.54 134.44 13 134.44 144.71 234.53 144.71 171.31 385.42 171.31 385.42 8 348.54 8"></polygon>
|
||||
<path d="M152.66,250.36 L0,134.36 L152.66,18.36 L152.66,89.61 L303.82,89.61 L303.82,0 L393.38,0 L393.38,179.11 L152.66,179.11 L152.66,250.36 Z M26.11,134.36 L136.85,218.52 L136.85,163.31 L377.58,163.31 L377.58,15.8 L319.63,15.8 L319.63,105.36 L136.86,105.36 L136.86,50.17 L26.11,134.36 Z" id="Shape" fill="#000000"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 43 KiB |
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="180px" height="246px" viewBox="0 0 180 246" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>digital-bitbox</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="digital-bitbox">
|
||||
<polygon id="Path-3" fill="#000000" points="90 0 126 17 126 107 180 132 180 206 90 246 0 206 0 132 54 107 54 17"></polygon>
|
||||
<polygon id="Path-4" fill="#FFFFFF" points="170 136 89.7558594 171.179688 10 136 25 130 90 158 155 130"></polygon>
|
||||
<polygon id="Path-5" fill="#FFFFFF" points="97 170 97 236 170 202"></polygon>
|
||||
<polygon id="Path-5" fill="#FFFFFF" transform="translate(46.500000, 203.000000) scale(-1, 1) translate(-46.500000, -203.000000) " points="10 170 10 236 83 202"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="155px" height="155px" viewBox="0 0 155 155" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>ledger</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ledger">
|
||||
<image opacity="0.139605978" x="0" y="0" width="154" height="155" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJoAAACbCAYAAACao4KyAAAABGdBTUEAA1teXP8meAAAB+FJREFUeAHtnM9y00YcgHdXSqBMO/VACldz64EZzJ/CDBecIeVaeALCExSeIPAEkCcgPEHTc2EwFw4tUGXaA7f6XALjzrQ0xZZUrYiMldhBiqzJrvfzRZG0u/r9vt83K3ltR4oaX81mq/H5wkKrzCX8wbteEDwLyvQp0vbrVrvp+36zSFsX2/y9uRl0u0Gvrtz9aQysi3hYibbw5i7HUdT0fK+933HD+LNO0ndxv/0n9Ts87y8rqVYmnXf9+NETx8XRE1dTDOEg7EiluiLsP92KROdV0OlW5bNv0fRs1TixsCwieUMp+XHWUl7VmOh/wASGE4WaXz6SxNL65tsgkW6197a3vt9Zr7RoqWDHF26JWHyvhGoIdcBUuHztBNKJRM0/aHy1cK91fGm19+fm/bLCldKkdXHpVnKxP/QtSKlEMl5OEdA117XXDmgXyiRfaEbTz2CHPO9BMoO1mcHK4J3NttuTzL3T565899ebN9eLzG6fnNFOnV9qH57zfx3et2eTHVntg4B2Ip3dWpc+PqNPGGdP0Vpn28vznnrCbXICPQ6L1I25I0+0K3vhmCia7qjm5h/s1ZlzENAE0me3xJW9ZBsrmr5dIhkSlSbg+fdaE26ju0RLV9Cl+KH0RejgPIHsNqqXwHbC2CVa+u6SpYudnNgvSEDL9uWxY7smqpxoem2Ed5cFidJsIgHt0M7ntaFo6XQXCT4LnIiPE6UIJM9ro7fQoWiN5GOl9B5bajQaQ2A8Ae2Sdio7m4qWmpd8dpkdZAuBqRBInMpmtVQ0/S0MZrOpoGWQEQLprHa0cU0f+nDrTL7qM3KePyEwNQKx9FK3lF43y32fbGqXYCAICKHfgerbp0q/GQsRCNRI4ItjjbbSX7+u8RoMDQHhSf+y0t/xhwUE6iQQx3FL8UlAnYgZWxOQyW9Khgu2IIFAXQT0Mgei1UWXcXMEEC2Hg526CCBaXWQZN0cA0XI42KmLAKLVRZZxcwQQLYeDnboIIFpdZBk3RwDRcjjYqYsAotVFlnFzBBAth4OdugggWl1kGTdHANFyONipiwCi1UWWcXMEfP3/SnNHDnhHShkccAhcvgYC/saLx1P/x8Q1xMmQlhPg1ml5AW0JH9FsqZTlcSKa5QW0JXxEs6VSlseJaJYX0JbwEc2WSlkeJ6JZXkBbwkc0WypleZyIZnkBbQkf0WyplOVxIprlBbQlfESzpVKWx4lolhfQlvARzZZKWR4nolleQFvCRzRbKmV5nIhmeQFtCR/RbKmU5XEimuUFtCV8RLOlUpbH6bcuLN0xKofBoBu87KwZFRPBVCbgK6lWKo8yxQFC6XWS4damOCRDGUCAW6cBRXAhBERzocoG5IhoBhTBhRAQzYUqG5AjohlQBBdCQDQXqmxAjohmQBFcCAHRXKiyATkimgFFcCEERHOhygbkiGgGFMGFEBDNhSobkCOiGVAEF0JANBeqbECOiGZAEVwIAdFcqLIBOSKaAUVwIQREc6HKBuSIaAYUwYUQEM2FKhuQI6IZUAQXQkA0F6psQI6IZkARXAgB0VyosgE5IpoBRXAhBERzocoG5IhoBhTBhRAQzYUqG5AjohlQBBdCQDQXqmxAjohmQBFcCAHRXKiyATkimgFFcCEERHOhygbkiGgGFMGFEPz3YbRoUqJ++G+vjni23g/WfN/v1DE2Y0IAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAjUSkGcvXo1rHL/00OEg7Gy8eDz1X2a1LizdUVKtlA6oxg5RHN0Nfn50Z9qXOH3uyhPP99rTHrfKePyuswo9+hYmgGiFUdGwCgFEq0KPvoUJIFphVDSsQgDRqtCjb2ECiFYYFQ2rEEC0KvToW5gAohVGRcMqBBCtCj36FiaAaIVR0bAKAUSrQo++hQkgWmFUNKxCANGq0KNvYQKIVhgVDasQUFEU1fLPiasERd/ZIhD1w66KoziYrbTIxjQCsRRdJaVENNMqM2PxSE8+VSIabMxYXqRjGIEwHASq97a3blhchDNjBH570VlX3W7Qi8IBss1YcU1JJ3Prw/JGFP1oSmDEMVsEwjh6qDNKRQtedtZY5pitApuQjV7W0LfNoWhpUFKsmhAcMcwSgfBuls3wkwH9+0JtYHaCLQSqENAu6TtlNsZQNH0glOHt7ARbCFQhMFDy5mj/nGj6fpq9SxhtxN8QKEMgCqP7vz9/1BntkxNNn+htvr0Z8bHUKCP+LkFAu9Pb3Bw+m2Vdd4mm19VE/59ENj5szyCxLUZAO7PV719PHdrRZZdo+nwQPAtE/90isu2gxe5EAqkriTOvgk53XKOxoumGyDYOF8fGEcgkS50Z1yA5NlE03V533OoPzvDMNoEeh0X6TPZ68+RekmlMe4qmG+ipsPf69aJ+J6H3eUEgI6CdCH756cy4Z7KsTbb9pGi6oR4oeP7o9vswWmRRN0Pn7lbPYtoF7URRCn7Rhrrd9trIydbZ9rIQ3oqa85r6OC83CKSPUGF/dXTFv2jmpUTLBt2+0Nqp80ttX8obQsTXlFKN7Dzb2SHwYeVBrg/i+OHORdgyWe5LtOwC2xfuJPs3W61LLXHoSFtE8nQcRU0ZiyYzXkbKjq1+LNLf75dKdYWKN8R/7zqfesgvmtn/iX/FXd1m5AIAAAAASUVORK5CYII="></image>
|
||||
<path d="M37,0 L37,37 L6.07153217e-16,37 L6.07153217e-16,21.9078947 C-0.100260417,17.3640351 2.48307292,12.5767544 7.75,7.54605263 C13.0169271,2.51535088 17.9335938,0 22.5,0 L37,0 Z" id="Path" fill="#000000"></path>
|
||||
<path d="M155,1.42108547e-14 L155,96 L59.0072885,96 L59,27 C59.2044271,21.3216146 61.7555339,15.4020182 66.6533203,9.24121094 C71.5511068,3.08040365 78,4.73695157e-15 86,1.42108547e-14 L155,1.42108547e-14 Z" id="Path" fill="#000000" transform="translate(107.000000, 48.000000) scale(-1, 1) translate(-107.000000, -48.000000) "></path>
|
||||
<path d="M37.0028093,118 L37.0028093,155 L0.00280933482,155 L0.00280933482,139.907895 C-0.0974510818,135.364035 2.48588225,130.576754 7.75280933,125.546053 C13.0197364,120.515351 17.9364031,118 22.5028093,118 L37.0028093,118 Z" id="Path" fill="#000000" transform="translate(18.501405, 136.500000) scale(1, -1) translate(-18.501405, -136.500000) "></path>
|
||||
<path d="M155.002809,118 L155.002809,155 L118.002809,155 L118.002809,139.907895 C117.902549,135.364035 120.485882,130.576754 125.752809,125.546053 C131.019736,120.515351 135.936403,118 140.502809,118 L155.002809,118 Z" id="Path" fill="#000000" transform="translate(136.501405, 136.500000) scale(-1, -1) translate(-136.501405, -136.500000) "></path>
|
||||
<polygon id="Path-2" fill="#000000" points="0 59 37 59 37 96 0 96"></polygon>
|
||||
<polygon id="Path-2" fill="#000000" points="59 118 96 118 96 155 59 155"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="317px" height="331px" viewBox="0 0 317 331" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>metamask</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="metamask" fill-rule="nonzero">
|
||||
<g id="Shape">
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="300 229 280.763485 294 243 283.678788"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="243 284 279.423237 233.834294 300 229"></polygon>
|
||||
<path d="M272,179.716489 L300,229.151839 L279.643454,234 L272,179.716489 Z M272,179.716489 L290.016713,167 L300,229.151839 L272,179.716489 Z" fill="#DDDDDD"></path>
|
||||
<path d="M249,142.560976 L301,121 L298.415663,132.810976 L249,142.560976 Z M296.771084,147 L249,142.560976 L298.415663,132.810976 L296.771084,147 Z" stroke="#000000" fill="#000000"></path>
|
||||
<path d="M296.851563,146.862549 L290.689098,167 L249,142.498282 L296.851563,146.862549 Z M306.893674,125.979381 L298,132.776632 L300.620458,121 L306.893674,125.979381 Z M296,147.147766 L297.667564,133 L304.734859,138.848797 L296,147.147766 Z" stroke="#000000" fill="#000000"></path>
|
||||
<path d="M290.501771,167 L296.750885,146.521729 L303,151.670968 L290.501771,167 Z M290.992432,167.10791 L235.792725,148.983398 L248.977783,141.554199 L290.992432,167.10791 Z" stroke="#000000" fill="#000000"></path>
|
||||
<polygon stroke="#000000" fill="#000000" points="261.963379 80.3129883 249 142.452436 236 149"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="301 121.351733 248.367188 143.222168 261.624512 80.5117188"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="262 81.2691652 317 75 300.998047 121.375"></polygon>
|
||||
<polygon fill="#DDDDDD" points="290.681641 166.88501 271.895501 180 235.297607 148.680664"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="313.882812 31.1132812 317 75.1660156 261.748047 81.559082"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="314 31 204.526123 111.583008 203 56.7398213"></polygon>
|
||||
<polygon fill="#DDDDDD" points="122 50 203.574427 56.4313725 205.114014 110.804199"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="236.105263 149 205 109.87239 262 81"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="235.673406 149 272.702148 180.192871 220.705078 185.546875"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="220.5 186.560059 205 110 236 148.993711"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="261.821661 81.9235353 205 111 314 31"></polygon>
|
||||
<polygon fill="#C0AD9E" points="122.280899 283.05679 148 310 113 278"></polygon>
|
||||
<polygon stroke="#999999" fill="#999999" points="243 284 255.077922 237.475513 279.552002 234.016846"></polygon>
|
||||
<polygon fill="#E2761B" points="18 153 63 106 23.8038869 147.391597"></polygon>
|
||||
<path d="M279.638916,234.052734 L254.892334,237.791992 L272.25025,180.452211 L279.638916,234.052734 Z M204.874875,110.555789 L161.302302,109.130947 L122,50 L204.874875,110.555789 Z" fill="#DDDDDD"></path>
|
||||
<polygon stroke="#AAAAAA" fill="#AAAAAA" points="272.860352 178.466309 255.035889 238.403076 253.430664 209.201538"></polygon>
|
||||
<polygon stroke="#999999" fill="#999999" points="221 185.534946 272 180 254.07465 209"></polygon>
|
||||
<polygon fill="#DDDDDD" points="161 109 205.040527 109.762451 221 186"></polygon>
|
||||
<path d="M162.666504,110.812012 L43.8779297,0 L122.686523,49.7369804 L162.666504,110.812012 Z M122.605469,298.883789 L20.4082031,331 L0,252.227539 L122.605469,298.883789 Z" stroke="#BBBBBB" fill="#BBBBBB"></path>
|
||||
<polygon stroke="#000000" fill="#000000" points="33 172 71.6265329 142 104 149.441253"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="104 150 72 142.487487 89.1344743 71"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="24 147.169713 72 142 33.4102142 172"></polygon>
|
||||
<polygon stroke="#999999" fill="#999999" points="254 209 234.057554 198.873754 221 185"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="24.0253906 147.559082 19.7363281 127.630127 72.6230469 142.035156"></polygon>
|
||||
<polygon fill="#000000" points="229.504639 222.02832 233.890869 198.618896 254.018799 208.942871"></polygon>
|
||||
<polygon fill="#DDDDDD" points="255 237 230 221.540845 253.676012 209"></polygon>
|
||||
<path d="M72.0783081,142.372253 L20.1834513,129.755756 L16,114.651652 L72.0783081,142.372253 Z M89.4462891,70.630127 L72.0721436,142.393555 L16,114.651652 L89.4462891,70.630127 Z M89,71 L161,109.037037 L103.773544,150 L89,71 Z" stroke="#000000" fill="#000000"></path>
|
||||
<path d="M102.939453,149.790527 L161.446777,108.118652 L187,187 L102.939453,149.790527 Z M187,187 L107.214355,184.98877 L103.306152,149.833008 L187,187 Z" stroke="#BBBBBB" fill="#BBBBBB"></path>
|
||||
<path d="M32.0712891,171.77832 L103.916504,149.303223 L107.426532,185.340426 L32.0712891,171.77832 Z M221,185.81459 L186.509656,187 L161.016793,109 L221,185.81459 Z" fill="#DDDDDD"></path>
|
||||
<polygon stroke="#999999" fill="#999999" points="234 198.617391 229.90303 221 221 185"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="44 0 161 109 88.697998 71.2009277"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="0 252.468262 98.902832 247.6875 122.921875 299.431641"></polygon>
|
||||
<polygon stroke="#999999" fill="#999999" points="122.545455 299 99 248.383808 148 246"></polygon>
|
||||
<path d="M230.672711,221.514124 L256.022229,237.016949 L265.330322,267.536621 L230.672711,221.514124 Z M108.110559,184.655367 L0,252.59887 L33.6414156,172 L108.110559,184.655367 Z M99.0289558,248.485876 L0,252.59887 L108.110559,184.655367 L99.0289558,248.485876 Z M221.749049,185.129944 L228.329102,209.351562 L196.20166,211.189697 L221.749049,185.129944 Z M196.142822,211.310303 L187.238959,186.316384 L221.749049,185.129944 L196.142822,211.310303 Z" stroke="#BBBBBB" fill="#BBBBBB"></path>
|
||||
<polygon fill="#FBFBFB" points="148.453925 309.720812 122 299 214 315"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="33.5405273 171.776367 18 152.599369 24.121582 146.518799"></polygon>
|
||||
<polygon fill="#FFFFFF" points="225.334473 302.741699 214.44873 315.716797 122 299"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="230.497559 275.182129 122 299 147.263158 246"></polygon>
|
||||
<polygon fill="#FFFFFF" points="122 299.011173 224.425587 276.239335 230 275 225.026316 303"></polygon>
|
||||
<path d="M16.5367115,114.898062 L13.1118164,53.6879883 L89.7861328,70.8154297 L16.5367115,114.898062 Z M24.1728516,147.156738 L13.6287487,137.309183 L20.7021717,130.023589 L24.1728516,147.156738 Z" stroke="#000000" fill="#000000"></path>
|
||||
<polygon stroke="#999999" fill="#999999" points="169 202.545455 186.879395 185.943848 184.428571 225"></polygon>
|
||||
<polygon stroke="#999999" fill="#999999" points="186 187 169.392822 203.094971 143 216"></polygon>
|
||||
<path d="M264.222892,266.476346 L230,222 L264.222892,266.476346 Z" stroke="#BBBBBB" fill="#BBBBBB"></path>
|
||||
<polygon stroke="#999999" fill="#999999" points="143.636727 216 108 185 187 186.677835"></polygon>
|
||||
<polygon stroke="#AAAAAA" fill="#AAAAAA" points="184 225 186.960205 185.625732 196.302979 211.109863"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="10 119.968586 16.6492537 114 21 129"></polygon>
|
||||
<polygon fill="#000000" points="185.06543 225.883789 142.477783 216.035645 168.803089 202"></polygon>
|
||||
<polygon stroke="#000000" fill="#000000" points="89 71 13 53.7854749 44.0444674 0"></polygon>
|
||||
<polygon fill="#FBFBFB" points="214.661621 314.608398 219.323242 326.847656 148.149902 309.338379"></polygon>
|
||||
<polygon fill="#DDDDDD" points="146.878378 247 143 216 184 225.323907"></polygon>
|
||||
<polygon stroke="#AAAAAA" fill="#AAAAAA" points="108 185 144.087824 215.460746 147.286621 246.429199"></polygon>
|
||||
<path d="M196.127808,211.066406 L228.076046,209.108317 L265,267 L196.127808,211.066406 Z M108.073194,185 L147.60076,246.618956 L98.7451172,248.597656 L108.073194,185 Z" fill="#DDDDDD"></path>
|
||||
<polygon stroke="#AAAAAA" fill="#AAAAAA" points="196.217929 211 235 270 183.407227 225.09375"></polygon>
|
||||
<polygon stroke="#BBBBBB" fill="#BBBBBB" points="183.709229 224.863281 235.486816 270.005859 228.958496 275.549316"></polygon>
|
||||
<path d="M229.403226,275 L147,246.555556 L183.877016,225.064198 L229.403226,275 Z M265.065247,267.038452 L235.010742,270.378784 L195.155273,209.339355 L265.065247,267.038452 Z" fill="#DDDDDD"></path>
|
||||
<path d="M272.00824,294.410675 L259.356948,320.362963 L218.346049,327 L272.00824,294.410675 Z M219.55719,327.282715 L214,315.148148 L225.11438,302.943848 L219.55719,327.282715 Z" fill="#FBFBFB"></path>
|
||||
<path d="M225.051514,302.968872 L232.809204,299.898437 L219,327 L225.051514,302.968872 Z M219,327 L232.710693,299.850464 L272.031677,294.415588 L219,327 Z" fill="#FBFBFB"></path>
|
||||
<polygon fill="#000000" points="265.13369 267 274.98291 272.750488 245 276"></polygon>
|
||||
<path d="M244.534709,276.545455 L235,270.340909 L265.137085,267.049438 L244.534709,276.545455 Z M241.146341,281.238636 L277,276.863636 L272.036133,294.474121 L241.146341,281.238636 Z" fill="#000000"></path>
|
||||
<path d="M272.066265,294.415274 L232.674699,299.928401 L241.36747,280.789976 L272.066265,294.415274 Z M232.858643,299.856323 L225,303 L229.64389,274.996735 L232.858643,299.856323 Z M229.646622,274.991623 L235.259036,270 L244.73494,276.143198 L229.646622,274.991623 Z M274.82019,272.612183 L277.009399,276.895203 L240.491821,281.35498 L274.82019,272.612183 Z" fill="#000000"></path>
|
||||
<path d="M241.280029,281.16687 L244.26709,275.812866 L274.876221,272.649902 L241.280029,281.16687 Z M229.641754,274.986938 L241.410302,281.092219 L232.735346,300 L229.641754,274.986938 Z" fill="#000000"></path>
|
||||
<polygon fill="#000000" points="244.553223 276.001831 241.346558 281.346558 229.65329 274.99649"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="260px" height="260px" viewBox="0 0 260 260" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>mist</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="mist">
|
||||
<circle id="Oval-3" fill="#000000" cx="130" cy="130" r="130"></circle>
|
||||
<g id="ether" transform="translate(66.000000, 26.000000)">
|
||||
<polygon id="Path-4" fill="#DDDDDD" points="0 106 63.9893369 0 64 77.1016184"></polygon>
|
||||
<polygon id="Path-5" fill="#AAAAAA" points="128 106 64.0106631 0 64 77.1016184"></polygon>
|
||||
<polygon id="Path-6" fill="#AAAAAA" points="64 77 0 105.714286 64 144"></polygon>
|
||||
<polygon id="Path-6" fill="#999999" points="64 77 128 105.714286 64 144"></polygon>
|
||||
<polygon id="Path-7" fill="#888888" points="128 117 64.0106631 208 64 155.541176"></polygon>
|
||||
<polygon id="Path-7" fill="#DDDDDD" points="0 117 63.9893369 208 64 155.541176"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="156px" height="258px" viewBox="0 0 156 258" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>trezor</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="trezor" fill="#000000" fill-rule="nonzero">
|
||||
<g id="path7">
|
||||
<path d="M78.1255028,0 C47.0321802,0 21.8688656,28.6428571 21.8688656,64.0357143 L21.8688656,88.0357143 C10.9501207,90.2857143 0,93.2857143 0,97.1785714 L0,222.428571 C0,222.428571 0,225.892857 3.41995173,227.535714 C15.8133548,233.25 64.5711987,252.892857 75.772325,257.392857 C77.2156074,258 77.6234916,258 78,258 C78.533387,258 78.7843926,258 80.227675,257.392857 C91.4288013,252.892857 140.312148,233.25 152.705551,227.535714 C155.874497,226.035714 156,222.571429 156,222.571429 L156,97.1785714 C156,93.2857143 145.206758,90.1428571 134.256637,88.0357143 L134.256637,64.0357143 C134.413516,28.6428571 109.093323,0 78.1255028,0 Z M78.1255028,30.6071429 C96.4489139,30.6071429 107.524537,43.2142857 107.524537,64.0714286 L107.524537,84.9285714 C86.9734513,83.2857143 69.4344328,83.2857143 48.7578439,84.9285714 L48.7578439,64.0714286 C48.7578439,43.1785714 59.8334674,30.6071429 78.1255028,30.6071429 Z M78,115.642857 C103.571199,115.642857 125.03218,117.892857 125.03218,121.928571 L125.03218,200.071429 C125.03218,201.285714 124.906677,201.428571 123.965406,201.857143 C123.055511,202.321429 80.3531778,219.857143 80.3531778,219.857143 C80.3531778,219.857143 78.6275141,220.464286 78.1255028,220.464286 C77.5921158,220.464286 75.8978278,219.714286 75.8978278,219.714286 C75.8978278,219.714286 33.1954948,202.178571 32.2855994,201.714286 C31.3757039,201.25 31.2188254,201.107143 31.2188254,199.928571 L31.2188254,121.785714 C30.9678198,117.75 52.4288013,115.642857 78,115.642857 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import { AddressFieldFactory } from './AddressFieldFactory';
|
||||
import { donationAddressMap } from 'config/data';
|
||||
import { Aux } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const AddressField: React.SFC<Props> = ({ isReadOnly }) => (
|
||||
<AddressFieldFactory
|
||||
withProps={({ currentTo, isValid, onChange, readOnly, errorMsg }) => (
|
||||
<Aux>
|
||||
<input
|
||||
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
value={currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{errorMsg && (
|
||||
<div className="has-error">
|
||||
<span className="help-block">{errorMsg}</span>
|
||||
</div>
|
||||
)}
|
||||
</Aux>
|
||||
)}
|
||||
/>
|
||||
);
|
|
@ -1 +0,0 @@
|
|||
export * from './AddressField';
|
|
@ -1,8 +1,9 @@
|
|||
import { Query } from 'components/renderCbs';
|
||||
import { setCurrentTo, TSetCurrentTo } from 'actions/transaction';
|
||||
import { AddressInput } from './AddressInput';
|
||||
import { AddressInputFactory } from './AddressInputFactory';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ICurrentTo } from 'selectors/transaction';
|
||||
|
||||
interface DispatchProps {
|
||||
setCurrentTo: TSetCurrentTo;
|
||||
|
@ -10,12 +11,21 @@ interface DispatchProps {
|
|||
|
||||
interface OwnProps {
|
||||
to: string | null;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
export interface CallbackProps {
|
||||
isValid: boolean;
|
||||
readOnly: boolean;
|
||||
currentTo: ICurrentTo;
|
||||
errorMsg?: string | null;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & DispatchProps & OwnProps;
|
||||
|
||||
//TODO: add ens resolving
|
||||
class AddressFieldClass extends React.Component<Props, {}> {
|
||||
class AddressFieldFactoryClass extends React.Component<Props, {}> {
|
||||
public componentDidMount() {
|
||||
// this 'to' parameter can be either token or actual field related
|
||||
const { to } = this.props;
|
||||
|
@ -25,7 +35,7 @@ class AddressFieldClass extends React.Component<Props, {}> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
return <AddressInput onChange={this.setAddress} />;
|
||||
return <AddressInputFactory onChange={this.setAddress} withProps={this.props.withProps} />;
|
||||
}
|
||||
|
||||
private setAddress = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
|
@ -34,10 +44,14 @@ class AddressFieldClass extends React.Component<Props, {}> {
|
|||
};
|
||||
}
|
||||
|
||||
const AddressField = connect(null, { setCurrentTo })(AddressFieldClass);
|
||||
const AddressField = connect(null, { setCurrentTo })(AddressFieldFactoryClass);
|
||||
|
||||
const DefaultAddressField: React.SFC<{}> = () => (
|
||||
<Query params={['to']} withQuery={({ to }) => <AddressField to={to} />} />
|
||||
interface DefaultAddressFieldProps {
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
const DefaultAddressField: React.SFC<DefaultAddressFieldProps> = ({ withProps }) => (
|
||||
<Query params={['to']} withQuery={({ to }) => <AddressField to={to} withProps={withProps} />} />
|
||||
);
|
||||
|
||||
export { DefaultAddressField as AddressField };
|
||||
export { DefaultAddressField as AddressFieldFactory };
|
|
@ -3,10 +3,10 @@ import { Identicon } from 'components/ui';
|
|||
import translate from 'translations';
|
||||
//import { EnsAddress } from './components';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { donationAddressMap } from 'config/data';
|
||||
import { ICurrentTo, getCurrentTo, isValidCurrentTo } from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { CallbackProps } from 'components/AddressFieldFactory';
|
||||
|
||||
interface StateProps {
|
||||
currentTo: ICurrentTo;
|
||||
|
@ -14,14 +14,15 @@ interface StateProps {
|
|||
}
|
||||
interface OwnProps {
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
//TODO: ENS handling
|
||||
class AddressInputClass extends Component<Props> {
|
||||
class AddressInputFactoryClass extends Component<Props> {
|
||||
public render() {
|
||||
const { currentTo, onChange, isValid } = this.props;
|
||||
const { currentTo, onChange, isValid, withProps } = this.props;
|
||||
const { raw } = currentTo;
|
||||
return (
|
||||
<div className="row form-group">
|
||||
|
@ -29,16 +30,15 @@ class AddressInputClass extends Component<Props> {
|
|||
<label>{translate('SEND_addr')}:</label>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<input
|
||||
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
value={raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
withQuery={({ readOnly }) =>
|
||||
withProps({
|
||||
currentTo,
|
||||
isValid,
|
||||
onChange,
|
||||
readOnly: !!readOnly,
|
||||
errorMsg: currentTo.error
|
||||
})
|
||||
}
|
||||
/>
|
||||
{/*<EnsAddress ensAddress={ensAddress} />*/}
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@ class AddressInputClass extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export const AddressInput = connect((state: AppState) => ({
|
||||
export const AddressInputFactory = connect((state: AppState) => ({
|
||||
currentTo: getCurrentTo(state),
|
||||
isValid: isValidCurrentTo(state)
|
||||
}))(AddressInputClass);
|
||||
}))(AddressInputFactoryClass);
|
|
@ -0,0 +1 @@
|
|||
export * from './AddressFieldFactory';
|
|
@ -6,26 +6,36 @@ import translate, { translateRaw } from 'translations';
|
|||
|
||||
interface Props {
|
||||
hasUnitDropdown?: boolean;
|
||||
showAllTokens?: boolean;
|
||||
customValidator?(rawAmount: string): boolean;
|
||||
}
|
||||
|
||||
export const AmountField: React.SFC<Props> = ({ hasUnitDropdown }) => (
|
||||
export const AmountField: React.SFC<Props> = ({
|
||||
hasUnitDropdown,
|
||||
showAllTokens,
|
||||
customValidator
|
||||
}) => (
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<Aux>
|
||||
<label>{translate('SEND_amount')}</label>
|
||||
|
||||
<div className="input-group">
|
||||
<input
|
||||
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
|
||||
className={`form-control ${
|
||||
isAmountValid(raw, customValidator, isValid) ? 'is-valid' : 'is-invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder={translateRaw('SEND_amount_short')}
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{hasUnitDropdown && <UnitDropDown />}
|
||||
{hasUnitDropdown && <UnitDropDown showAllTokens={showAllTokens} />}
|
||||
</div>
|
||||
</Aux>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
const isAmountValid = (raw, customValidator, isValid) =>
|
||||
customValidator ? customValidator(raw) : isValid;
|
||||
|
|
|
@ -11,7 +11,7 @@ export interface CallbackProps {
|
|||
currentValue:
|
||||
| AppState['transaction']['fields']['value']
|
||||
| AppState['transaction']['meta']['tokenValue'];
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>);
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { connect } from 'react-redux';
|
|||
import { CallbackProps } from 'components/AmountFieldFactory';
|
||||
|
||||
interface OwnProps {
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>);
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
|
|||
private decimalLookup: { [key: string]: number } = {};
|
||||
private requestedCurrencies: string[] | null = null;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
this.makeBalanceLookup(props);
|
||||
|
||||
|
@ -41,7 +41,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
|
|||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps) {
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
const { balance, tokenBalances } = this.props;
|
||||
if (nextProps.balance !== balance || nextProps.tokenBalances !== tokenBalances) {
|
||||
this.makeBalanceLookup(nextProps);
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import React from 'react';
|
||||
import { forceOfflineConfig as dForceOfflineConfig, TForceOfflineConfig } from 'actions/config';
|
||||
import OfflineSymbol from 'components/ui/OfflineSymbol';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
type sizeType = 'small' | 'medium' | 'large';
|
||||
|
||||
interface OfflineToggleProps {
|
||||
offline: boolean;
|
||||
forceOffline: boolean;
|
||||
forceOfflineConfig: TForceOfflineConfig;
|
||||
size?: sizeType;
|
||||
}
|
||||
|
||||
class OfflineToggle extends React.Component<OfflineToggleProps, {}> {
|
||||
public render() {
|
||||
const { forceOfflineConfig, offline, forceOffline, size } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!offline ? (
|
||||
<div className="row text-center">
|
||||
<div className="col-xs-3">
|
||||
<OfflineSymbol offline={offline || forceOffline} size={size} />
|
||||
</div>
|
||||
<div className="col-xs-6">
|
||||
<button className="btn-xs btn-info" onClick={forceOfflineConfig}>
|
||||
{forceOffline ? 'Go Online' : 'Go Offline'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<h5>You are currently offline.</h5>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
offline: state.config.offline,
|
||||
forceOffline: state.config.forceOffline
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
forceOfflineConfig: dForceOfflineConfig
|
||||
})(OfflineToggle);
|
|
@ -12,15 +12,19 @@ interface Props {
|
|||
toggleForm(): void;
|
||||
}
|
||||
|
||||
interface IGenerateSymbolLookup {
|
||||
[tokenSymbol: string]: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
tokenSymbolLookup: { [symbol: string]: boolean };
|
||||
tokenSymbolLookup: IGenerateSymbolLookup;
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimal: string;
|
||||
}
|
||||
|
||||
export default class AddCustomTokenForm extends React.Component<Props, State> {
|
||||
public state = {
|
||||
public state: State = {
|
||||
tokenSymbolLookup: {},
|
||||
address: '',
|
||||
symbol: '',
|
||||
|
@ -130,14 +134,14 @@ export default class AddCustomTokenForm extends React.Component<Props, State> {
|
|||
return !Object.keys(this.getErrors()).length && address && symbol && decimal;
|
||||
}
|
||||
|
||||
public onFieldChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
public onFieldChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
// TODO: typescript bug: https://github.com/Microsoft/TypeScript/issues/13948
|
||||
const name: any = (e.target as HTMLInputElement).name;
|
||||
const value = (e.target as HTMLInputElement).value;
|
||||
const name: any = e.currentTarget.name;
|
||||
const value = e.currentTarget.value;
|
||||
this.setState({ [name]: value });
|
||||
};
|
||||
|
||||
public onSave = (ev: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
public onSave = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (!this.isValid()) {
|
||||
return;
|
||||
|
@ -148,9 +152,12 @@ export default class AddCustomTokenForm extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
private generateSymbolLookup(tokens: Token[]) {
|
||||
return tokens.reduce((prev, tk) => {
|
||||
prev[tk.symbol] = true;
|
||||
return prev;
|
||||
}, {});
|
||||
return tokens.reduce(
|
||||
(prev, tk) => {
|
||||
prev[tk.symbol] = true;
|
||||
return prev;
|
||||
},
|
||||
{} as IGenerateSymbolLookup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@ export default class TokenBalances extends React.Component<Props, State> {
|
|||
const { showCustomTokenForm, trackedTokens } = this.state;
|
||||
|
||||
let bottom;
|
||||
if (!hasSavedWalletTokens) {
|
||||
let help;
|
||||
if (tokenBalances.length && !hasSavedWalletTokens) {
|
||||
help = 'Select which tokens you would like to keep track of';
|
||||
bottom = (
|
||||
<div className="TokenBalances-buttons">
|
||||
<button className="btn btn-primary btn-block" onClick={this.handleSetWalletTokens}>
|
||||
|
@ -76,28 +78,31 @@ export default class TokenBalances extends React.Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
{!hasSavedWalletTokens && (
|
||||
<p className="TokenBalances-help">Select which tokens you would like to keep track of</p>
|
||||
{help && <p className="TokenBalances-help">{help}</p>}
|
||||
|
||||
{tokenBalances.length ? (
|
||||
<table className="TokenBalances-rows">
|
||||
<tbody>
|
||||
{tokenBalances.map(
|
||||
token =>
|
||||
token ? (
|
||||
<TokenRow
|
||||
key={token.symbol}
|
||||
balance={token.balance}
|
||||
symbol={token.symbol}
|
||||
custom={token.custom}
|
||||
decimal={token.decimal}
|
||||
tracked={trackedTokens[token.symbol]}
|
||||
toggleTracked={!hasSavedWalletTokens && this.toggleTrack}
|
||||
onRemove={this.props.onRemoveCustomToken}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="well well-sm text-center">No tokens found</div>
|
||||
)}
|
||||
<table className="TokenBalances-rows">
|
||||
<tbody>
|
||||
{tokenBalances.map(
|
||||
token =>
|
||||
token ? (
|
||||
<TokenRow
|
||||
key={token.symbol}
|
||||
balance={token.balance}
|
||||
symbol={token.symbol}
|
||||
custom={token.custom}
|
||||
decimal={token.decimal}
|
||||
tracked={trackedTokens[token.symbol]}
|
||||
toggleTracked={!hasSavedWalletTokens && this.toggleTrack}
|
||||
onRemove={this.props.onRemoveCustomToken}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
{bottom}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class TokenRow extends React.Component<Props, State> {
|
|||
{!!custom && (
|
||||
<img
|
||||
src={removeIcon}
|
||||
className="TokenRow-balance-remove"
|
||||
className="TokenRow-symbol-remove"
|
||||
title="Remove Token"
|
||||
onClick={this.onRemove}
|
||||
tabIndex={0}
|
||||
|
@ -65,7 +65,7 @@ export default class TokenRow extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
public toggleShowLongBalance = (e: React.SyntheticEvent<HTMLTableDataCellElement>) => {
|
||||
public toggleShowLongBalance = (e: React.FormEvent<HTMLTableDataCellElement>) => {
|
||||
e.preventDefault();
|
||||
this.setState(state => {
|
||||
return {
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&-none {
|
||||
text-align: center;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
&-loader {
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
|
|
|
@ -38,7 +38,7 @@ interface ActionProps {
|
|||
}
|
||||
type Props = StateProps & ActionProps;
|
||||
|
||||
class TokenBalances extends React.Component<Props, {}> {
|
||||
class TokenBalances extends React.Component<Props> {
|
||||
public render() {
|
||||
const {
|
||||
tokens,
|
||||
|
@ -96,6 +96,7 @@ class TokenBalances extends React.Component<Props, {}> {
|
|||
private scanWalletForTokens = () => {
|
||||
if (this.props.wallet) {
|
||||
this.props.scanWalletForTokens(this.props.wallet);
|
||||
this.setState({ hasScanned: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import AccountInfo from './AccountInfo';
|
|||
import EquivalentValues from './EquivalentValues';
|
||||
import Promos from './Promos';
|
||||
import TokenBalances from './TokenBalances';
|
||||
import OfflineToggle from './OfflineToggle';
|
||||
|
||||
interface Props {
|
||||
wallet: IWallet;
|
||||
|
@ -37,10 +36,6 @@ export class BalanceSidebar extends React.Component<Props, {}> {
|
|||
}
|
||||
|
||||
const blocks: Block[] = [
|
||||
{
|
||||
name: 'Go Offline',
|
||||
content: <OfflineToggle />
|
||||
},
|
||||
{
|
||||
name: 'Account Info',
|
||||
content: <AccountInfo wallet={wallet} balance={balance} network={network} />
|
||||
|
|
|
@ -9,10 +9,10 @@ export interface CallBackProps {
|
|||
data: AppState['transaction']['fields']['data'];
|
||||
dataExists: boolean;
|
||||
readOnly: boolean;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>);
|
||||
onChange(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>): void;
|
||||
}
|
||||
interface DispatchProps {
|
||||
isEtherTransaction;
|
||||
isEtherTransaction: boolean;
|
||||
inputData: TInputData;
|
||||
}
|
||||
interface OwnProps {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { CallBackProps } from 'components/DataFieldFactory';
|
|||
|
||||
interface OwnProps {
|
||||
withProps(props: CallBackProps): React.ReactElement<any> | null;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>);
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
interface StateProps {
|
||||
data: AppState['transaction']['fields']['data'];
|
||||
|
|
|
@ -15,7 +15,7 @@ import PreFooter from './PreFooter';
|
|||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import { NewTabLink } from 'components/ui';
|
||||
|
||||
const AffiliateTag = ({ link, text }) => {
|
||||
const AffiliateTag = ({ link, text }: Link) => {
|
||||
return (
|
||||
<li className="Footer-affiliate-tag" key={link}>
|
||||
<NewTabLink href={link}>{text}</NewTabLink>
|
||||
|
@ -23,7 +23,7 @@ const AffiliateTag = ({ link, text }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const SocialMediaLink = ({ link, text }) => {
|
||||
const SocialMediaLink = ({ link, text }: Link) => {
|
||||
return (
|
||||
<NewTabLink className="Footer-social-media-link" key={link} href={link}>
|
||||
<i className={`sm-icon sm-logo-${text} sm-24px`} />
|
||||
|
@ -108,7 +108,7 @@ interface State {
|
|||
}
|
||||
|
||||
export default class Footer extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { isOpen: false };
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './GasFieldFactory';
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { GasFieldFactory } from './GasFieldFactory';
|
||||
import { GasLimitFieldFactory } from './GasLimitFieldFactory';
|
||||
import translate from 'translations';
|
||||
import { Aux } from 'components/ui';
|
||||
|
||||
export const GasField: React.SFC<{}> = () => (
|
||||
export const GasLimitField: React.SFC<{}> = () => (
|
||||
<Aux>
|
||||
<label>{translate('TRANS_gas')} </label>
|
||||
<GasFieldFactory
|
||||
<GasLimitFieldFactory
|
||||
withProps={({ gasLimit: { raw, value }, onChange, readOnly }) => (
|
||||
<input
|
||||
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { GasQuery } from 'components/renderCbs';
|
||||
import { GasInput } from './GasInputFactory';
|
||||
import { GasLimitInput } from './GasLimitInputFactory';
|
||||
import { inputGasLimit, TInputGasLimit } from 'actions/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
@ -18,7 +18,7 @@ interface DispatchProps {
|
|||
}
|
||||
interface OwnProps {
|
||||
gasLimit: string | null;
|
||||
withProps(props: CallBackProps);
|
||||
withProps(props: CallBackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps;
|
||||
|
@ -34,7 +34,7 @@ class GasLimitFieldClass extends Component<Props, {}> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
return <GasInput onChange={this.setGas} withProps={this.props.withProps} />;
|
||||
return <GasLimitInput onChange={this.setGas} withProps={this.props.withProps} />;
|
||||
}
|
||||
|
||||
private setGas = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
|
@ -45,13 +45,13 @@ class GasLimitFieldClass extends Component<Props, {}> {
|
|||
|
||||
const GasLimitField = connect(null, { inputGasLimit })(GasLimitFieldClass);
|
||||
|
||||
interface DefaultGasFieldProps {
|
||||
withProps(props: CallBackProps);
|
||||
interface DefaultGasLimitFieldProps {
|
||||
withProps(props: CallBackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
const DefaultGasField: React.SFC<DefaultGasFieldProps> = ({ withProps }) => (
|
||||
const DefaultGasLimitField: React.SFC<DefaultGasLimitFieldProps> = ({ withProps }) => (
|
||||
<GasQuery
|
||||
withQuery={({ gasLimit }) => <GasLimitField gasLimit={gasLimit} withProps={withProps} />}
|
||||
/>
|
||||
);
|
||||
|
||||
export { DefaultGasField as GasFieldFactory };
|
||||
export { DefaultGasLimitField as GasLimitFieldFactory };
|
|
@ -3,7 +3,7 @@ import { Query } from 'components/renderCbs';
|
|||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { getGasLimit } from 'selectors/transaction';
|
||||
import { CallBackProps } from 'components/GasFieldFactory';
|
||||
import { CallBackProps } from 'components/GasLimitFieldFactory';
|
||||
|
||||
interface StateProps {
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||
|
@ -15,7 +15,7 @@ interface OwnProps {
|
|||
}
|
||||
|
||||
type Props = StateProps & OwnProps;
|
||||
class GasInputClass extends Component<Props> {
|
||||
class GasLimitInputClass extends Component<Props> {
|
||||
public render() {
|
||||
const { gasLimit, onChange } = this.props;
|
||||
return (
|
||||
|
@ -29,6 +29,6 @@ class GasInputClass extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export const GasInput = connect((state: AppState) => ({ gasLimit: getGasLimit(state) }))(
|
||||
GasInputClass
|
||||
export const GasLimitInput = connect((state: AppState) => ({ gasLimit: getGasLimit(state) }))(
|
||||
GasLimitInputClass
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
export * from './GasLimitFieldFactory';
|
|
@ -0,0 +1,10 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.GasSlider {
|
||||
&-toggle {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-top: $space-sm;
|
||||
left: -8px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import React from 'react';
|
||||
import { translateRaw } from 'translations';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
inputGasPrice,
|
||||
TInputGasPrice,
|
||||
inputGasLimit,
|
||||
TInputGasLimit,
|
||||
inputNonce,
|
||||
TInputNonce
|
||||
} from 'actions/transaction';
|
||||
import { fetchCCRates, TFetchCCRates } from 'actions/rates';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import SimpleGas from './components/SimpleGas';
|
||||
import AdvancedGas from './components/AdvancedGas';
|
||||
import './GasSlider.scss';
|
||||
|
||||
interface Props {
|
||||
// Component configuration
|
||||
disableAdvanced?: boolean;
|
||||
// Data
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||
offline: AppState['config']['offline'];
|
||||
network: AppState['config']['network'];
|
||||
// Actions
|
||||
inputGasPrice: TInputGasPrice;
|
||||
inputGasLimit: TInputGasLimit;
|
||||
inputNonce: TInputNonce;
|
||||
fetchCCRates: TFetchCCRates;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showAdvanced: boolean;
|
||||
}
|
||||
|
||||
class GasSlider extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
showAdvanced: false
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.fetchCCRates([this.props.network.unit]);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { gasPrice, gasLimit, offline, disableAdvanced } = this.props;
|
||||
const showAdvanced = (this.state.showAdvanced || offline) && !disableAdvanced;
|
||||
|
||||
return (
|
||||
<div className="GasSlider">
|
||||
{showAdvanced ? (
|
||||
<AdvancedGas
|
||||
gasPrice={gasPrice.raw}
|
||||
gasLimit={gasLimit.raw}
|
||||
changeGasPrice={this.props.inputGasPrice}
|
||||
changeGasLimit={this.props.inputGasLimit}
|
||||
/>
|
||||
) : (
|
||||
<SimpleGas gasPrice={gasPrice.raw} changeGasPrice={this.props.inputGasPrice} />
|
||||
)}
|
||||
|
||||
{!offline &&
|
||||
!disableAdvanced && (
|
||||
<div className="help-block">
|
||||
<a className="GasSlider-toggle" onClick={this.toggleAdvanced}>
|
||||
<strong>
|
||||
{showAdvanced
|
||||
? `- ${translateRaw('Back to simple')}`
|
||||
: `+ ${translateRaw('Advanced: Data, Gas Price, Gas Limit')}`}
|
||||
</strong>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleAdvanced = () => {
|
||||
this.setState({ showAdvanced: !this.state.showAdvanced });
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
gasPrice: state.transaction.fields.gasPrice,
|
||||
gasLimit: state.transaction.fields.gasLimit,
|
||||
offline: state.config.offline,
|
||||
network: getNetworkConfig(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
inputGasPrice,
|
||||
inputGasLimit,
|
||||
inputNonce,
|
||||
fetchCCRates
|
||||
})(GasSlider);
|
|
@ -0,0 +1,4 @@
|
|||
.AdvancedGas {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { DataFieldFactory } from 'components/DataFieldFactory';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './AdvancedGas.scss';
|
||||
|
||||
interface Props {
|
||||
gasPrice: string;
|
||||
gasLimit: string;
|
||||
changeGasPrice(gwei: string): void;
|
||||
changeGasLimit(wei: string): void;
|
||||
}
|
||||
|
||||
export default class AdvancedGas extends React.Component<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="AdvancedGas row form-group">
|
||||
<div className="col-md-3 col-sm-6 col-xs-12">
|
||||
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
value={this.props.gasPrice}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-3 col-sm-6 col-xs-12">
|
||||
<label>{translate('OFFLINE_Step2_Label_4')}</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
value={this.props.gasLimit}
|
||||
onChange={this.handleGasLimitChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-sm-12">
|
||||
<label>{translate('OFFLINE_Step2_Label_6')}</label>
|
||||
<DataFieldFactory
|
||||
withProps={({ data, onChange }) => (
|
||||
<input
|
||||
className="form-control"
|
||||
value={data.raw}
|
||||
onChange={onChange}
|
||||
placeholder="0x7cB57B5A..."
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-sm-12">
|
||||
<FeeSummary
|
||||
render={({ gasPriceWei, gasLimit, fee, usd }) => (
|
||||
<span>
|
||||
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.props.changeGasPrice(ev.currentTarget.value);
|
||||
};
|
||||
|
||||
private handleGasLimitChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.props.changeGasLimit(ev.currentTarget.value);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.FeeSummary {
|
||||
background: $gray-lighter;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
padding: 0 12px;
|
||||
font-family: $font-family-monospace;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react';
|
||||
import BN from 'bn.js';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
import './FeeSummary.scss';
|
||||
|
||||
interface RenderData {
|
||||
gasPriceWei: string;
|
||||
gasPriceGwei: string;
|
||||
gasLimit: string;
|
||||
fee: React.ReactElement<string>;
|
||||
usd: React.ReactElement<string> | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
// Redux props
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||
rates: AppState['rates']['rates'];
|
||||
network: AppState['config']['network'];
|
||||
// Component props
|
||||
render(data: RenderData): React.ReactElement<string> | string;
|
||||
}
|
||||
|
||||
class FeeSummary extends React.Component<Props> {
|
||||
public render() {
|
||||
const { gasPrice, gasLimit, rates, network } = this.props;
|
||||
|
||||
const feeBig = gasPrice.value && gasLimit.value && gasPrice.value.mul(gasLimit.value);
|
||||
const fee = (
|
||||
<UnitDisplay
|
||||
value={feeBig}
|
||||
unit="ether"
|
||||
symbol={network.unit}
|
||||
displayShortBalance={6}
|
||||
checkOffline={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const usdBig = network.isTestnet
|
||||
? new BN(0)
|
||||
: feeBig && rates[network.unit] && feeBig.muln(rates[network.unit].USD);
|
||||
const usd = (
|
||||
<UnitDisplay
|
||||
value={usdBig}
|
||||
unit="ether"
|
||||
displayShortBalance={2}
|
||||
displayTrailingZeroes={true}
|
||||
checkOffline={true}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="FeeSummary">
|
||||
{this.props.render({
|
||||
gasPriceWei: gasPrice.value.toString(),
|
||||
gasPriceGwei: gasPrice.raw,
|
||||
fee,
|
||||
usd,
|
||||
gasLimit: gasLimit.raw
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
gasPrice: state.transaction.fields.gasPrice,
|
||||
gasLimit: state.transaction.fields.gasLimit,
|
||||
rates: state.rates.rates,
|
||||
network: getNetworkConfig(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(FeeSummary);
|
|
@ -0,0 +1,36 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.SimpleGas {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
&-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&-slider {
|
||||
padding-top: 8px;
|
||||
margin-bottom: $space-xs;
|
||||
|
||||
&-labels {
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
|
||||
> span {
|
||||
flex: 1;
|
||||
padding: 0 $space-xs;
|
||||
text-align: center;
|
||||
color: $gray-light;
|
||||
font-size: $font-size-xs;
|
||||
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
import Slider from 'rc-slider';
|
||||
import translate from 'translations';
|
||||
import { gasPriceDefaults } from 'config/data';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './SimpleGas.scss';
|
||||
|
||||
interface Props {
|
||||
gasPrice: string;
|
||||
changeGasPrice(gwei: string): void;
|
||||
}
|
||||
|
||||
export default class SimpleGas extends React.Component<Props> {
|
||||
public render() {
|
||||
const { gasPrice } = this.props;
|
||||
|
||||
return (
|
||||
<div className="SimpleGas row form-group">
|
||||
<div className="col-md-12">
|
||||
<label className="SimpleGas-label">{translate('Transaction Fee')}</label>
|
||||
</div>
|
||||
|
||||
<div className="col-md-8 col-sm-12">
|
||||
<div className="SimpleGas-slider">
|
||||
<Slider
|
||||
onChange={this.handleSlider}
|
||||
min={gasPriceDefaults.gasPriceMinGwei}
|
||||
max={gasPriceDefaults.gasPriceMaxGwei}
|
||||
value={parseFloat(gasPrice)}
|
||||
/>
|
||||
<div className="SimpleGas-slider-labels">
|
||||
<span>{translate('Cheap')}</span>
|
||||
<span>{translate('Balanced')}</span>
|
||||
<span>{translate('Fast')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4 col-sm-12">
|
||||
<FeeSummary
|
||||
render={({ fee, usd }) => (
|
||||
<span>
|
||||
{fee} {usd && <span>/ ${usd}</span>}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handleSlider = (gasGwei: number) => {
|
||||
this.props.changeGasPrice(gasGwei.toString());
|
||||
};
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import GasSlider from './GasSlider';
|
||||
export default GasSlider;
|
|
@ -233,7 +233,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||
'form-control': true,
|
||||
'is-invalid': this.state[input.name] && invalids[input.name]
|
||||
})}
|
||||
value={this.state[name]}
|
||||
value={this.state[input.name]}
|
||||
onChange={this.handleChange}
|
||||
{...input}
|
||||
/>
|
||||
|
@ -252,7 +252,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||
customNetworkUnit,
|
||||
customNetworkChainId
|
||||
} = this.state;
|
||||
const required = ['name', 'url', 'port', 'network'];
|
||||
const required: (keyof State)[] = ['name', 'url', 'port', 'network'];
|
||||
const invalids: { [key: string]: boolean } = {};
|
||||
|
||||
// Required fields
|
||||
|
@ -344,7 +344,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||
|
||||
private handleCheckbox = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { name } = ev.currentTarget;
|
||||
this.setState({ [name as any]: !this.state[name] });
|
||||
this.setState({ [name as any]: !this.state[name as keyof State] });
|
||||
};
|
||||
|
||||
private saveAndAdd = () => {
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import React, { Component } from 'react';
|
||||
import NavigationLink from './NavigationLink';
|
||||
import { knowledgeBaseURL } from 'config/data';
|
||||
|
||||
import './Navigation.scss';
|
||||
|
||||
const tabs = [
|
||||
export interface TabLink {
|
||||
name: string;
|
||||
to: string;
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
const tabs: TabLink[] = [
|
||||
{
|
||||
name: 'NAV_GenerateWallet',
|
||||
to: '/'
|
||||
to: '/generate'
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -90,7 +95,7 @@ export default class Navigation extends Component<Props, State> {
|
|||
<div className="Navigation-scroll container">
|
||||
<ul className="Navigation-links">
|
||||
{tabs.map(link => {
|
||||
return <NavigationLink key={link.name} link={link} />;
|
||||
return <NavigationLink key={link.name} link={link} isHomepage={link === tabs[0]} />;
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -44,3 +44,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#NAV_Swap a:before {
|
||||
content:"";
|
||||
display: inline-block;
|
||||
margin-top: -.1rem;
|
||||
width: 1.3rem;
|
||||
height: 1.3rem;
|
||||
background-image: url('~assets/images/swap.svg');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { TabLink } from './Navigation';
|
||||
import './NavigationLink.scss';
|
||||
|
||||
interface Props {
|
||||
link: {
|
||||
name: string;
|
||||
to?: string;
|
||||
external?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface InjectedLocation extends Props {
|
||||
location: { pathname: string };
|
||||
interface Props extends RouteComponentProps<{}> {
|
||||
link: TabLink;
|
||||
isHomepage: boolean;
|
||||
}
|
||||
|
||||
class NavigationLink extends React.Component<Props, {}> {
|
||||
get injected() {
|
||||
return this.props as InjectedLocation;
|
||||
}
|
||||
public render() {
|
||||
const { link } = this.props;
|
||||
const { location } = this.injected;
|
||||
const { link, location, isHomepage } = this.props;
|
||||
const isExternalLink = link.to.includes('http');
|
||||
let isActive = false;
|
||||
|
||||
if (!isExternalLink) {
|
||||
// isActive if
|
||||
// 1) Current path is the same as link
|
||||
// 2) the first path is the same for both links (/account and /account/send)
|
||||
// 3) we're at the root path and this is the "homepage" nav item
|
||||
const isSubRoute = location.pathname.split('/')[1] === link.to.split('/')[1];
|
||||
isActive =
|
||||
location.pathname === link.to || isSubRoute || (isHomepage && location.pathname === '/');
|
||||
}
|
||||
|
||||
const linkClasses = classnames({
|
||||
'NavigationLink-link': true,
|
||||
'is-disabled': !link.to,
|
||||
'is-active': location.pathname === link.to
|
||||
'is-active': isActive
|
||||
});
|
||||
const linkLabel = `nav item: ${translateRaw(link.name)}`;
|
||||
|
||||
|
@ -41,9 +44,13 @@ class NavigationLink extends React.Component<Props, {}> {
|
|||
</Link>
|
||||
);
|
||||
|
||||
return <li className="NavigationLink">{linkEl}</li>;
|
||||
return (
|
||||
<li id={link.name} className="NavigationLink">
|
||||
{linkEl}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// withRouter is a HOC which provides NavigationLink with a react-router location prop
|
||||
export default withRouter(NavigationLink);
|
||||
export default withRouter<Props>(NavigationLink);
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import { AppState } from 'reducers';
|
||||
import { resetWallet, TResetWallet } from 'actions/wallet';
|
||||
|
||||
interface Props extends RouteComponentProps<{}> {
|
||||
// State
|
||||
wallet: AppState['wallet']['inst'];
|
||||
// Actions
|
||||
resetWallet: TResetWallet;
|
||||
}
|
||||
|
||||
interface State {
|
||||
nextLocation: RouteComponentProps<{}>['location'] | null;
|
||||
openModal: boolean;
|
||||
}
|
||||
|
||||
class LogOutPromptClass extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
nextLocation: null,
|
||||
openModal: false
|
||||
};
|
||||
|
||||
this.props.history.block(nextLocation => {
|
||||
if (this.props.wallet && nextLocation.pathname !== this.props.location.pathname) {
|
||||
const isSubTab =
|
||||
nextLocation.pathname.split('/')[1] === this.props.location.pathname.split('/')[1];
|
||||
if (!isSubTab) {
|
||||
this.setState({
|
||||
openModal: true,
|
||||
nextLocation
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const buttons: IButton[] = [
|
||||
{ text: 'Log Out', type: 'primary', onClick: this.onConfirm },
|
||||
{ text: 'Cancel', type: 'default', onClick: this.onCancel }
|
||||
];
|
||||
return (
|
||||
<Modal
|
||||
title="You are about to log out"
|
||||
isOpen={this.state.openModal}
|
||||
handleClose={this.onCancel}
|
||||
buttons={buttons}
|
||||
>
|
||||
<p>Leaving this page will log you out. Are you sure you want to continue?</p>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private onCancel = () => {
|
||||
this.setState({ nextLocation: null, openModal: false });
|
||||
};
|
||||
|
||||
private onConfirm = () => {
|
||||
const { nextLocation } = this.state;
|
||||
this.props.resetWallet();
|
||||
this.setState(
|
||||
{
|
||||
openModal: false,
|
||||
nextLocation: null
|
||||
},
|
||||
() => {
|
||||
if (nextLocation) {
|
||||
this.props.history.push(nextLocation.pathname);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return { wallet: state.wallet.inst };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
resetWallet
|
||||
})(withRouter<Props>(LogOutPromptClass));
|
|
@ -27,8 +27,8 @@ class NonceInputClass extends Component<Props> {
|
|||
const { nonce: { raw, value }, onChange, shouldDisplay } = this.props;
|
||||
const content = (
|
||||
<Aux>
|
||||
{nonceHelp}
|
||||
<label>Nonce</label>
|
||||
{nonceHelp}
|
||||
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
|
|
|
@ -6,10 +6,10 @@ import { connect } from 'react-redux';
|
|||
import { AppState } from 'reducers';
|
||||
|
||||
interface Props {
|
||||
allowReadOnly: boolean;
|
||||
disabledWallets?: string[];
|
||||
}
|
||||
export const OfflineAwareUnlockHeader: React.SFC<Props> = ({ allowReadOnly }) => (
|
||||
<UnlockHeader title={<Title />} allowReadOnly={allowReadOnly} />
|
||||
export const OfflineAwareUnlockHeader: React.SFC<Props> = ({ disabledWallets }) => (
|
||||
<UnlockHeader title={<Title />} disabledWallets={disabledWallets} />
|
||||
);
|
||||
|
||||
interface StateProps {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.PageNotFound {
|
||||
text-align: center;
|
||||
&-content {
|
||||
padding: 100px 0;
|
||||
font-size: 30px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import TabSection from '../../containers/TabSection/index';
|
||||
import './PageNotFound.scss';
|
||||
|
||||
const PageNotFound: React.SFC<RouteComponentProps<{}>> = () => (
|
||||
<TabSection>
|
||||
<section className="Tab-content PageNotFound">
|
||||
<div className="Tab-content-pane">
|
||||
<h1 className="PageNotFound-header">/ᐠ≗ᆽ≗ᐟ \</h1>
|
||||
<main role="main">
|
||||
<p className="PageNotFound-content">
|
||||
Meow! Something went wrong and the page you were looking for doesn't yet exist. Try the{' '}
|
||||
<Link to="/">home page</Link>.
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
</TabSection>
|
||||
);
|
||||
|
||||
export default PageNotFound;
|
|
@ -0,0 +1,3 @@
|
|||
import PageNotFound from './PageNotFound';
|
||||
|
||||
export default PageNotFound;
|
|
@ -1,7 +1,7 @@
|
|||
import { PaperWallet } from 'components';
|
||||
import { IFullWallet } from 'ethereumjs-wallet';
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { translateRaw } from 'translations';
|
||||
import printElement from 'utils/printElement';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
|
||||
|
@ -39,13 +39,13 @@ const PrintableWallet: React.SFC<{ wallet: IFullWallet }> = ({ wallet }) => {
|
|||
<PaperWallet address={address} privateKey={privateKey} />
|
||||
<a
|
||||
role="button"
|
||||
aria-label={translate('x_Print')}
|
||||
aria-label={translateRaw('x_Print')}
|
||||
aria-describedby="x_PrintDesc"
|
||||
className={'btn btn-lg btn-primary'}
|
||||
className="btn btn-lg btn-primary btn-block"
|
||||
onClick={print(address, privateKey)}
|
||||
style={{ marginTop: 10 }}
|
||||
style={{ margin: '10px auto 0', maxWidth: '260px' }}
|
||||
>
|
||||
{translate('x_Print')}
|
||||
{translateRaw('x_Print')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,8 +2,11 @@ import React from 'react';
|
|||
import { SendButtonFactory } from './SendButtonFactory';
|
||||
import translate from 'translations';
|
||||
|
||||
export const SendButton: React.SFC<{}> = () => (
|
||||
export const SendButton: React.SFC<{ onlyTransactionParameters?: boolean }> = ({
|
||||
onlyTransactionParameters
|
||||
}) => (
|
||||
<SendButtonFactory
|
||||
onlyTransactionParameters={!!onlyTransactionParameters}
|
||||
withProps={({ onClick }) => (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
|
|
|
@ -18,6 +18,7 @@ interface StateProps {
|
|||
walletType: IWalletType;
|
||||
}
|
||||
interface OwnProps {
|
||||
onlyTransactionParameters?: boolean;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
|
@ -27,11 +28,13 @@ const getStringifiedTx = (serializedTransaction: string) =>
|
|||
type Props = StateProps & OwnProps;
|
||||
class SendButtonFactoryClass extends Component<Props> {
|
||||
public render() {
|
||||
const { onlyTransactionParameters } = this.props;
|
||||
const columnSize = onlyTransactionParameters ? 12 : 6;
|
||||
return (
|
||||
<SerializedTransaction
|
||||
withSerializedTransaction={serializedTransaction => (
|
||||
<Aux>
|
||||
<div className="col-sm-6">
|
||||
<div className={`col-sm-${columnSize}`}>
|
||||
<label>
|
||||
{this.props.walletType.isWeb3Wallet
|
||||
? 'Transaction Parameters'
|
||||
|
@ -44,19 +47,21 @@ class SendButtonFactoryClass extends Component<Props> {
|
|||
readOnly={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<label>
|
||||
{this.props.walletType.isWeb3Wallet
|
||||
? 'Serialized Transaction Parameters'
|
||||
: translate('SEND_signed')}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={addHexPrefix(serializedTransaction)}
|
||||
rows={4}
|
||||
readOnly={true}
|
||||
/>
|
||||
</div>
|
||||
{!onlyTransactionParameters && (
|
||||
<div className="col-sm-6">
|
||||
<label>
|
||||
{this.props.walletType.isWeb3Wallet
|
||||
? 'Serialized Transaction Parameters'
|
||||
: translate('SEND_signed')}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={addHexPrefix(serializedTransaction)}
|
||||
rows={4}
|
||||
readOnly={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<OfflineBroadcast />
|
||||
<OnlineSend withProps={this.props.withProps} />
|
||||
</Aux>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||
import { setUnitMeta, TSetUnitMeta } from 'actions/transaction';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
import { withConditional } from 'components/hocs';
|
||||
import { TokenBalance, getShownTokenBalances } from 'selectors/wallet';
|
||||
import { TokenBalance, MergedToken, getShownTokenBalances, getTokens } from 'selectors/wallet';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
@ -15,6 +15,8 @@ interface DispatchProps {
|
|||
interface StateProps {
|
||||
unit: string;
|
||||
tokens: TokenBalance[];
|
||||
allTokens: MergedToken[];
|
||||
showAllTokens?: boolean;
|
||||
}
|
||||
|
||||
const StringDropdown = Dropdown as new () => Dropdown<string>;
|
||||
|
@ -22,14 +24,15 @@ const ConditionalStringDropDown = withConditional(StringDropdown);
|
|||
|
||||
class UnitDropdownClass extends Component<DispatchProps & StateProps> {
|
||||
public render() {
|
||||
const { tokens, unit } = this.props;
|
||||
const { tokens, allTokens, showAllTokens, unit } = this.props;
|
||||
const focusedTokens = showAllTokens ? allTokens : tokens;
|
||||
return (
|
||||
<div className="input-group-btn">
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<ConditionalStringDropDown
|
||||
options={['ether', ...getTokenSymbols(tokens)]}
|
||||
options={['ether', ...getTokenSymbols(focusedTokens)]}
|
||||
value={unit}
|
||||
condition={!readOnly}
|
||||
conditionalProps={{
|
||||
|
@ -46,11 +49,12 @@ class UnitDropdownClass extends Component<DispatchProps & StateProps> {
|
|||
this.props.setUnitMeta(unit);
|
||||
};
|
||||
}
|
||||
const getTokenSymbols = (tokens: TokenBalance[]) => tokens.map(t => t.symbol);
|
||||
const getTokenSymbols = (tokens: (TokenBalance | MergedToken)[]) => tokens.map(t => t.symbol);
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
tokens: getShownTokenBalances(state, true),
|
||||
allTokens: getTokens(state),
|
||||
unit: getUnit(state)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import { isKeystorePassRequired } from 'libs/wallet';
|
||||
import React, { Component } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
|
||||
export interface KeystoreValue {
|
||||
file: string;
|
||||
password: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
function isPassRequired(file: string): boolean {
|
||||
let passReq = false;
|
||||
try {
|
||||
passReq = isKeystorePassRequired(file);
|
||||
} catch (e) {
|
||||
// TODO: communicate invalid file to user
|
||||
}
|
||||
return passReq;
|
||||
}
|
||||
|
||||
export default class KeystoreDecrypt extends Component {
|
||||
public props: {
|
||||
value: KeystoreValue;
|
||||
onChange(value: KeystoreValue): void;
|
||||
onUnlock(): void;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { file, password } = this.props.value;
|
||||
const passReq = isPassRequired(file);
|
||||
|
||||
return (
|
||||
<section className="col-md-4 col-sm-6">
|
||||
<div id="selectedUploadKey">
|
||||
<h4>{translate('ADD_Radio_2_alt')}</h4>
|
||||
|
||||
<div className="form-group">
|
||||
<input
|
||||
className={'hidden'}
|
||||
type="file"
|
||||
id="fselector"
|
||||
onChange={this.handleFileSelection}
|
||||
/>
|
||||
<label htmlFor="fselector" style={{ width: '100%' }}>
|
||||
<a className="btn btn-default btn-block" id="aria1" tabIndex={0} role="button">
|
||||
{translate('ADD_Radio_2_short')}
|
||||
</a>
|
||||
</label>
|
||||
<div className={file.length && passReq ? '' : 'hidden'}>
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
public onKeyDown = (e: any) => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onUnlock();
|
||||
}
|
||||
};
|
||||
|
||||
public onPasswordChange = (e: any) => {
|
||||
const valid = this.props.value.file.length && e.target.value.length;
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
password: e.target.value,
|
||||
valid
|
||||
});
|
||||
};
|
||||
|
||||
public handleFileSelection = (e: any) => {
|
||||
const fileReader = new FileReader();
|
||||
const target = e.target;
|
||||
const inputFile = target.files[0];
|
||||
|
||||
fileReader.onload = () => {
|
||||
const keystore = fileReader.result;
|
||||
const passReq = isPassRequired(keystore);
|
||||
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
file: keystore,
|
||||
valid: keystore.length && !passReq
|
||||
});
|
||||
};
|
||||
|
||||
fileReader.readAsText(inputFile, 'utf-8');
|
||||
};
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import { Location, History } from 'history';
|
||||
|
||||
interface Props {
|
||||
when: boolean;
|
||||
onConfirm?: any;
|
||||
onCancel?: any;
|
||||
}
|
||||
|
||||
interface InjectedProps extends Props {
|
||||
location: Location;
|
||||
history: History;
|
||||
}
|
||||
|
||||
interface State {
|
||||
nextLocation: Location | null;
|
||||
openModal: boolean;
|
||||
}
|
||||
|
||||
class NavigationPrompt extends React.Component<Props, State> {
|
||||
public unblock;
|
||||
get injected() {
|
||||
return this.props as InjectedProps;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
nextLocation: null,
|
||||
openModal: false
|
||||
};
|
||||
}
|
||||
|
||||
public setupUnblock() {
|
||||
this.unblock = this.injected.history.block(nextLocation => {
|
||||
if (this.props.when && nextLocation.pathname !== this.injected.location.pathname) {
|
||||
const isSubTab =
|
||||
nextLocation.pathname.split('/')[1] === this.injected.location.pathname.split('/')[1];
|
||||
if (!isSubTab) {
|
||||
this.setState({
|
||||
openModal: true,
|
||||
nextLocation
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.setupUnblock();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.unblock();
|
||||
}
|
||||
|
||||
public onCancel = () => {
|
||||
if (this.props.onCancel) {
|
||||
this.props.onCancel();
|
||||
}
|
||||
this.setState({ nextLocation: null, openModal: false });
|
||||
};
|
||||
|
||||
public onConfirm = () => {
|
||||
if (this.props.onConfirm) {
|
||||
this.props.onConfirm();
|
||||
}
|
||||
// Lock Wallet
|
||||
this.navigateToNextLocation();
|
||||
};
|
||||
|
||||
public navigateToNextLocation() {
|
||||
this.unblock();
|
||||
if (this.state.nextLocation) {
|
||||
this.injected.history.push(this.state.nextLocation.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const buttons: IButton[] = [
|
||||
{ text: 'Log Out', type: 'primary', onClick: this.onConfirm },
|
||||
{ text: 'Cancel', type: 'default', onClick: this.onCancel }
|
||||
];
|
||||
return (
|
||||
<Modal
|
||||
title="You are about to log out"
|
||||
isOpen={this.state.openModal}
|
||||
handleClose={this.onCancel}
|
||||
buttons={buttons}
|
||||
>
|
||||
<p>Leaving this page will log you out. Are you sure you want to continue?</p>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(NavigationPrompt);
|
|
@ -1,113 +0,0 @@
|
|||
import { isValidEncryptedPrivKey, isValidPrivKey } from 'libs/validators';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
import React, { Component } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
|
||||
export interface PrivateKeyValue {
|
||||
key: string;
|
||||
password: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
interface Validated {
|
||||
fixedPkey: string;
|
||||
isValidPkey: boolean;
|
||||
isPassRequired: boolean;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
function validatePkeyAndPass(pkey: string, pass: string): Validated {
|
||||
const fixedPkey = stripHexPrefix(pkey);
|
||||
const validPkey = isValidPrivKey(fixedPkey);
|
||||
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
|
||||
const isValidPkey = validPkey || validEncPkey;
|
||||
|
||||
let isValidPass = false;
|
||||
|
||||
if (validPkey) {
|
||||
isValidPass = true;
|
||||
} else if (validEncPkey) {
|
||||
isValidPass = pass.length > 0;
|
||||
}
|
||||
|
||||
return {
|
||||
fixedPkey,
|
||||
isValidPkey,
|
||||
isPassRequired: validEncPkey,
|
||||
valid: isValidPkey && isValidPass
|
||||
};
|
||||
}
|
||||
|
||||
export default class PrivateKeyDecrypt extends Component {
|
||||
public props: {
|
||||
value: PrivateKeyValue;
|
||||
onChange(value: PrivateKeyValue): void;
|
||||
onUnlock(): void;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { key, password } = this.props.value;
|
||||
const { isValidPkey, isPassRequired } = validatePkeyAndPass(key, password);
|
||||
|
||||
return (
|
||||
<section className="col-md-4 col-sm-6">
|
||||
<div id="selectedTypeKey">
|
||||
<h4>{translate('ADD_Radio_3')}</h4>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
id="aria-private-key"
|
||||
className={`form-control ${isValidPkey ? 'is-valid' : 'is-invalid'}`}
|
||||
value={key}
|
||||
onChange={this.onPkeyChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_PrivKey2')}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
{isValidPkey &&
|
||||
isPassRequired && (
|
||||
<div className="form-group">
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
public onPkeyChange = (e: React.SyntheticEvent<HTMLTextAreaElement>) => {
|
||||
const pkey = (e.target as HTMLInputElement).value;
|
||||
const pass = this.props.value.password;
|
||||
const { fixedPkey, valid } = validatePkeyAndPass(pkey, pass);
|
||||
|
||||
this.props.onChange({ ...this.props.value, key: fixedPkey, valid });
|
||||
};
|
||||
|
||||
public onPasswordChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const pkey = this.props.value.key;
|
||||
const pass = (e.target as HTMLInputElement).value;
|
||||
const { valid } = validatePkeyAndPass(pkey, pass);
|
||||
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
password: pass,
|
||||
valid
|
||||
});
|
||||
};
|
||||
|
||||
public onKeyDown = (e: any) => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onUnlock();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import translate from 'translations';
|
||||
import { donationAddressMap } from 'config/data';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { AddressOnlyWallet } from 'libs/wallet';
|
||||
|
||||
interface Props {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export default class ViewOnlyDecrypt extends Component<Props, State> {
|
||||
public state = {
|
||||
address: ''
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { address } = this.state;
|
||||
const isValid = isValidETHAddress(address);
|
||||
|
||||
return (
|
||||
<section className="col-md-4 col-sm-6">
|
||||
<div id="selectedUploadKey">
|
||||
<h4>{translate('MYWAL_Address')}</h4>
|
||||
|
||||
<form className="form-group" onSubmit={this.openWallet}>
|
||||
<input
|
||||
className={`form-control
|
||||
${isValid ? 'is-valid' : 'is-invalid'}
|
||||
`}
|
||||
onChange={this.changeAddress}
|
||||
value={address}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
/>
|
||||
|
||||
<button className="btn btn-primary btn-block" disabled={!isValid}>
|
||||
{translate('NAV_ViewWallet')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
private changeAddress = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({ address: ev.currentTarget.value });
|
||||
};
|
||||
|
||||
private openWallet = (ev: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
const { address } = this.state;
|
||||
ev.preventDefault();
|
||||
if (isValidETHAddress(address)) {
|
||||
const wallet = new AddressOnlyWallet(address);
|
||||
this.props.onUnlock(wallet);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
$speed: 500ms;
|
||||
|
||||
@keyframes decrypt-enter {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin decrypt-title {
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
margin: 0 0 30px;
|
||||
font-weight: normal;
|
||||
animation: decrypt-enter $speed ease 1;
|
||||
}
|
||||
|
||||
.WalletDecrypt {
|
||||
position: relative;
|
||||
|
||||
&-wallets {
|
||||
&-title {
|
||||
@include decrypt-title;
|
||||
}
|
||||
|
||||
&-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
|
||||
@media screen and (max-width: $screen-xs) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-decrypt {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding-bottom: $space;
|
||||
|
||||
&-back {
|
||||
@include reset-button;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
line-height: $font-size-large;
|
||||
opacity: 0.4;
|
||||
transition: opacity 120ms ease, transform 120ms ease;
|
||||
|
||||
@media (max-width: $screen-md) {
|
||||
top: auto;
|
||||
bottom: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.fa {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@include decrypt-title;
|
||||
}
|
||||
|
||||
&-form {
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation between two slides
|
||||
.DecryptContent {
|
||||
&-enter {
|
||||
opacity: 0;
|
||||
transition: opacity $speed * .25 ease $speed * .125;
|
||||
|
||||
&-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-exit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
transition: opacity $speed * .25 ease;
|
||||
pointer-events: none;
|
||||
|
||||
&-active {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||
import {
|
||||
setWallet,
|
||||
TSetWallet,
|
||||
unlockKeystore,
|
||||
TUnlockKeystore,
|
||||
unlockMnemonic,
|
||||
TUnlockMnemonic,
|
||||
unlockPrivateKey,
|
||||
TUnlockPrivateKey,
|
||||
unlockWeb3,
|
||||
TUnlockWeb3,
|
||||
resetWallet,
|
||||
TResetWallet
|
||||
} from 'actions/wallet';
|
||||
import { reset, TReset } from 'actions/transaction';
|
||||
import translate from 'translations';
|
||||
import {
|
||||
DigitalBitboxDecrypt,
|
||||
KeystoreDecrypt,
|
||||
LedgerNanoSDecrypt,
|
||||
MnemonicDecrypt,
|
||||
PrivateKeyDecrypt,
|
||||
PrivateKeyValue,
|
||||
TrezorDecrypt,
|
||||
ViewOnlyDecrypt,
|
||||
Web3Decrypt,
|
||||
WalletButton
|
||||
} from './components';
|
||||
import { AppState } from 'reducers';
|
||||
import { knowledgeBaseURL, isWeb3NodeAvailable } from 'config/data';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import DigitalBitboxIcon from 'assets/images/wallets/digital-bitbox.svg';
|
||||
import LedgerIcon from 'assets/images/wallets/ledger.svg';
|
||||
import MetamaskIcon from 'assets/images/wallets/metamask.svg';
|
||||
import MistIcon from 'assets/images/wallets/mist.svg';
|
||||
import TrezorIcon from 'assets/images/wallets/trezor.svg';
|
||||
import './WalletDecrypt.scss';
|
||||
type UnlockParams = {} | PrivateKeyValue;
|
||||
|
||||
interface Props {
|
||||
resetTransactionState: TReset;
|
||||
unlockKeystore: TUnlockKeystore;
|
||||
unlockMnemonic: TUnlockMnemonic;
|
||||
unlockPrivateKey: TUnlockPrivateKey;
|
||||
setWallet: TSetWallet;
|
||||
unlockWeb3: TUnlockWeb3;
|
||||
resetWallet: TResetWallet;
|
||||
wallet: IWallet;
|
||||
hidden?: boolean;
|
||||
offline: boolean;
|
||||
disabledWallets?: string[];
|
||||
}
|
||||
|
||||
interface State {
|
||||
selectedWalletKey: string | null;
|
||||
value: UnlockParams | null;
|
||||
}
|
||||
|
||||
interface BaseWalletInfo {
|
||||
lid: string;
|
||||
component: any;
|
||||
initialParams: object;
|
||||
unlock: any;
|
||||
helpLink?: string;
|
||||
isReadOnly?: boolean;
|
||||
attemptUnlock?: boolean;
|
||||
}
|
||||
|
||||
export interface SecureWalletInfo extends BaseWalletInfo {
|
||||
icon?: string | null;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface InsecureWalletInfo extends BaseWalletInfo {
|
||||
example: string;
|
||||
}
|
||||
|
||||
const WEB3_TYPES = {
|
||||
MetamaskInpageProvider: {
|
||||
lid: 'x_MetaMask',
|
||||
icon: MetamaskIcon
|
||||
},
|
||||
EthereumProvider: {
|
||||
lid: 'x_Mist',
|
||||
icon: MistIcon
|
||||
}
|
||||
};
|
||||
const WEB3_TYPE: string | false =
|
||||
(window as any).web3 && (window as any).web3.currentProvider.constructor.name;
|
||||
|
||||
const SECURE_WALLETS = ['web3', 'ledger-nano-s', 'trezor', 'digital-bitbox'];
|
||||
const INSECURE_WALLETS = ['private-key', 'keystore-file', 'mnemonic-phrase'];
|
||||
|
||||
export class WalletDecrypt extends Component<Props, State> {
|
||||
public WALLETS: { [key: string]: SecureWalletInfo | InsecureWalletInfo } = {
|
||||
web3: {
|
||||
lid: WEB3_TYPE ? WEB3_TYPES[WEB3_TYPE].lid : 'x_Web3',
|
||||
icon: WEB3_TYPE && WEB3_TYPES[WEB3_TYPE].icon,
|
||||
description: 'ADD_Web3Desc',
|
||||
component: Web3Decrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.unlockWeb3,
|
||||
attemptUnlock: true,
|
||||
helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask`
|
||||
},
|
||||
'ledger-nano-s': {
|
||||
lid: 'x_Ledger',
|
||||
icon: LedgerIcon,
|
||||
description: 'ADD_HardwareDesc',
|
||||
component: LedgerNanoSDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
helpLink:
|
||||
'https://ledger.zendesk.com/hc/en-us/articles/115005200009-How-to-use-MyEtherWallet-with-Ledger'
|
||||
},
|
||||
trezor: {
|
||||
lid: 'x_Trezor',
|
||||
icon: TrezorIcon,
|
||||
description: 'ADD_HardwareDesc',
|
||||
component: TrezorDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
helpLink: 'https://doc.satoshilabs.com/trezor-apps/mew.html'
|
||||
},
|
||||
'digital-bitbox': {
|
||||
lid: 'x_DigitalBitbox',
|
||||
icon: DigitalBitboxIcon,
|
||||
description: 'ADD_HardwareDesc',
|
||||
component: DigitalBitboxDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
helpLink: 'https://digitalbitbox.com/ethereum'
|
||||
},
|
||||
'keystore-file': {
|
||||
lid: 'x_Keystore2',
|
||||
example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8',
|
||||
component: KeystoreDecrypt,
|
||||
initialParams: {
|
||||
file: '',
|
||||
password: ''
|
||||
},
|
||||
unlock: this.props.unlockKeystore,
|
||||
helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html`
|
||||
},
|
||||
'mnemonic-phrase': {
|
||||
lid: 'x_Mnemonic',
|
||||
example: 'brain surround have swap horror cheese file distinct',
|
||||
component: MnemonicDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.unlockMnemonic,
|
||||
helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html`
|
||||
},
|
||||
'private-key': {
|
||||
lid: 'x_PrivKey2',
|
||||
example: 'f1d0e0789c6d40f399ca90cc674b7858de4c719e0d5752a60d5d2f6baa45d4c9',
|
||||
component: PrivateKeyDecrypt,
|
||||
initialParams: {
|
||||
key: '',
|
||||
password: ''
|
||||
},
|
||||
unlock: this.props.unlockPrivateKey,
|
||||
helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html`
|
||||
},
|
||||
'view-only': {
|
||||
lid: 'View Address',
|
||||
example: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
|
||||
component: ViewOnlyDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
helpLink: '',
|
||||
isReadOnly: true
|
||||
}
|
||||
};
|
||||
public state: State = {
|
||||
selectedWalletKey: null,
|
||||
value: null
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps) {
|
||||
// Reset state when unlock is hidden / revealed
|
||||
if (nextProps.hidden !== this.props.hidden) {
|
||||
this.setState({
|
||||
value: null,
|
||||
selectedWalletKey: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getSelectedWallet() {
|
||||
const { selectedWalletKey } = this.state;
|
||||
if (!selectedWalletKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.WALLETS[selectedWalletKey];
|
||||
}
|
||||
|
||||
public getDecryptionComponent() {
|
||||
const selectedWallet = this.getSelectedWallet();
|
||||
if (!selectedWallet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<selectedWallet.component
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
onUnlock={this.onUnlock}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public isOnlineRequiredWalletAndOffline(selectedWalletKey) {
|
||||
const onlineRequiredWallets = ['trezor', 'ledger-nano-s'];
|
||||
return this.props.offline && onlineRequiredWallets.includes(selectedWalletKey);
|
||||
}
|
||||
|
||||
public buildWalletOptions() {
|
||||
const viewOnly = this.WALLETS['view-only'] as InsecureWalletInfo;
|
||||
|
||||
return (
|
||||
<div className="WalletDecrypt-wallets">
|
||||
<h2 className="WalletDecrypt-wallets-title">{translate('decrypt_Access')}</h2>
|
||||
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{SECURE_WALLETS.map(type => {
|
||||
const wallet = this.WALLETS[type] as SecureWalletInfo;
|
||||
return (
|
||||
<WalletButton
|
||||
key={type}
|
||||
name={translate(wallet.lid)}
|
||||
description={translate(wallet.description)}
|
||||
icon={wallet.icon}
|
||||
helpLink={wallet.helpLink}
|
||||
walletType={type}
|
||||
isSecure={true}
|
||||
isDisabled={this.isWalletDisabled(type)}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{INSECURE_WALLETS.map(type => {
|
||||
const wallet = this.WALLETS[type] as InsecureWalletInfo;
|
||||
return (
|
||||
<WalletButton
|
||||
key={type}
|
||||
name={translate(wallet.lid)}
|
||||
example={wallet.example}
|
||||
helpLink={wallet.helpLink}
|
||||
walletType={type}
|
||||
isSecure={false}
|
||||
isDisabled={this.isWalletDisabled(type)}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<WalletButton
|
||||
key="view-only"
|
||||
name={translate(viewOnly.lid)}
|
||||
example={viewOnly.example}
|
||||
helpLink={viewOnly.helpLink}
|
||||
walletType="view-only"
|
||||
isReadOnly={true}
|
||||
isDisabled={this.isWalletDisabled('view-only')}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public handleWalletChoice = async (walletType: string) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
if (!wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
let timeout = 0;
|
||||
const web3Available = await isWeb3NodeAvailable();
|
||||
if (wallet.attemptUnlock && web3Available) {
|
||||
// timeout is only the maximum wait time before secondary view is shown
|
||||
// send view will be shown immediately on web3 resolve
|
||||
timeout = 1000;
|
||||
wallet.unlock();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
selectedWalletKey: walletType,
|
||||
value: wallet.initialParams
|
||||
});
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
public clearWalletChoice = () => {
|
||||
this.setState({
|
||||
selectedWalletKey: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { hidden } = this.props;
|
||||
const selectedWallet = this.getSelectedWallet();
|
||||
const decryptionComponent = this.getDecryptionComponent();
|
||||
return (
|
||||
<div>
|
||||
{!hidden && (
|
||||
<article className="Tab-content-pane">
|
||||
<div className="WalletDecrypt">
|
||||
<TransitionGroup>
|
||||
{decryptionComponent && selectedWallet ? (
|
||||
<CSSTransition classNames="DecryptContent" timeout={500} key="decrypt">
|
||||
<div className="WalletDecrypt-decrypt">
|
||||
<button
|
||||
className="WalletDecrypt-decrypt-back"
|
||||
onClick={this.clearWalletChoice}
|
||||
>
|
||||
<i className="fa fa-arrow-left" /> {translate('Change Wallet')}
|
||||
</button>
|
||||
<h2 className="WalletDecrypt-decrypt-title">
|
||||
{!selectedWallet.isReadOnly && 'Unlock your'}{' '}
|
||||
{translate(selectedWallet.lid)}
|
||||
</h2>
|
||||
<section className="WalletDecrypt-decrypt-form">
|
||||
{decryptionComponent}
|
||||
</section>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
) : (
|
||||
<CSSTransition classNames="DecryptContent" timeout={500} key="wallets">
|
||||
{this.buildWalletOptions()}
|
||||
</CSSTransition>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public onChange = (value: UnlockParams) => {
|
||||
this.setState({ value });
|
||||
};
|
||||
|
||||
public onUnlock = (payload: any) => {
|
||||
const { value, selectedWalletKey } = this.state;
|
||||
if (!selectedWalletKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// some components (TrezorDecrypt) don't take an onChange prop, and thus
|
||||
// this.state.value will remain unpopulated. in this case, we can expect
|
||||
// the payload to contain the unlocked wallet info.
|
||||
const unlockValue = value && !isEmpty(value) ? value : payload;
|
||||
this.WALLETS[selectedWalletKey].unlock(unlockValue);
|
||||
this.props.resetTransactionState();
|
||||
};
|
||||
|
||||
private isWalletDisabled = (walletKey: string) => {
|
||||
if (!this.props.disabledWallets) {
|
||||
return false;
|
||||
}
|
||||
return this.props.disabledWallets.indexOf(walletKey) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
offline: state.config.offline,
|
||||
wallet: state.wallet.inst
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
unlockKeystore,
|
||||
unlockMnemonic,
|
||||
unlockPrivateKey,
|
||||
unlockWeb3,
|
||||
setWallet,
|
||||
resetWallet,
|
||||
resetTransactionState: reset
|
||||
})(WalletDecrypt);
|
|
@ -30,8 +30,8 @@ interface Props {
|
|||
seed?: string;
|
||||
|
||||
// Redux state
|
||||
wallets: DeterministicWalletData[];
|
||||
desiredToken: string;
|
||||
wallets: AppState['deterministicWallets']['wallets'];
|
||||
desiredToken: AppState['deterministicWallets']['desiredToken'];
|
||||
network: NetworkConfig;
|
||||
tokens: MergedToken[];
|
||||
|
||||
|
@ -52,7 +52,7 @@ interface State {
|
|||
page: number;
|
||||
}
|
||||
|
||||
class DeterministicWalletsModal extends React.Component<Props, State> {
|
||||
class DeterministicWalletsModalClass extends React.Component<Props, State> {
|
||||
public state = {
|
||||
selectedAddress: '',
|
||||
selectedAddrIndex: 0,
|
||||
|
@ -200,8 +200,8 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private handleChangePath = (ev: React.SyntheticEvent<HTMLSelectElement>) => {
|
||||
const { value } = ev.target as HTMLSelectElement;
|
||||
private handleChangePath = (ev: React.FormEvent<HTMLSelectElement>) => {
|
||||
const { value } = ev.currentTarget;
|
||||
|
||||
if (value === 'custom') {
|
||||
this.setState({ isCustomPath: true });
|
||||
|
@ -213,11 +213,11 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
private handleChangeCustomPath = (ev: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.setState({ customPath: (ev.target as HTMLInputElement).value });
|
||||
private handleChangeCustomPath = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({ customPath: ev.currentTarget.value });
|
||||
};
|
||||
|
||||
private handleSubmitCustomPath = (ev: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
private handleSubmitCustomPath = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (!isValidPath(this.state.customPath)) {
|
||||
return;
|
||||
|
@ -225,8 +225,8 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
this.props.onPathChange(this.state.customPath);
|
||||
};
|
||||
|
||||
private handleChangeToken = (ev: React.SyntheticEvent<HTMLSelectElement>) => {
|
||||
this.props.setDesiredToken((ev.target as HTMLSelectElement).value || undefined);
|
||||
private handleChangeToken = (ev: React.FormEvent<HTMLSelectElement>) => {
|
||||
this.props.setDesiredToken(ev.currentTarget.value || undefined);
|
||||
};
|
||||
|
||||
private handleConfirmAddress = () => {
|
||||
|
@ -252,7 +252,7 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
const { selectedAddress } = this.state;
|
||||
|
||||
// Get renderable values, but keep 'em short
|
||||
const token = wallet.tokenValues[desiredToken];
|
||||
const token = desiredToken ? wallet.tokenValues[desiredToken] : null;
|
||||
|
||||
return (
|
||||
<tr
|
||||
|
@ -310,7 +310,7 @@ function mapStateToProps(state: AppState) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
export const DeterministicWalletsModal = connect(mapStateToProps, {
|
||||
getDeterministicWallets,
|
||||
setDesiredToken
|
||||
})(DeterministicWalletsModal);
|
||||
})(DeterministicWalletsModalClass);
|
|
@ -0,0 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
export class DigitalBitboxDecrypt extends React.Component<{}, {}> {
|
||||
public render() {
|
||||
return <strong>Not yet implemented</strong>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import { isKeystorePassRequired } from 'libs/wallet';
|
||||
import React, { Component } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
|
||||
export interface KeystoreValue {
|
||||
file: string;
|
||||
password: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
function isPassRequired(file: string): boolean {
|
||||
let passReq = false;
|
||||
try {
|
||||
passReq = isKeystorePassRequired(file);
|
||||
} catch (e) {
|
||||
// TODO: communicate invalid file to user
|
||||
}
|
||||
return passReq;
|
||||
}
|
||||
|
||||
export class KeystoreDecrypt extends Component {
|
||||
public props: {
|
||||
value: KeystoreValue;
|
||||
onChange(value: KeystoreValue): void;
|
||||
onUnlock(): void;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { file, password } = this.props.value;
|
||||
const passReq = isPassRequired(file);
|
||||
const unlockDisabled = !file || (passReq && !password);
|
||||
|
||||
return (
|
||||
<form id="selectedUploadKey" onSubmit={this.unlock}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
className={'hidden'}
|
||||
type="file"
|
||||
id="fselector"
|
||||
onChange={this.handleFileSelection}
|
||||
/>
|
||||
<label htmlFor="fselector" style={{ width: '100%' }}>
|
||||
<a className="btn btn-default btn-block" id="aria1" tabIndex={0} role="button">
|
||||
{translate('ADD_Radio_2_short')}
|
||||
</a>
|
||||
</label>
|
||||
<div className={file.length && passReq ? '' : 'hidden'}>
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-primary btn-block" disabled={unlockDisabled}>
|
||||
{translate('ADD_Label_6_short')}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
private onKeyDown = (e: any) => {
|
||||
if (e.keyCode === 13) {
|
||||
this.unlock(e);
|
||||
}
|
||||
};
|
||||
|
||||
private unlock = (e: React.SyntheticEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onUnlock();
|
||||
};
|
||||
|
||||
private onPasswordChange = (e: any) => {
|
||||
const valid = this.props.value.file.length && e.target.value.length;
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
password: e.target.value,
|
||||
valid
|
||||
});
|
||||
};
|
||||
|
||||
private handleFileSelection = (e: any) => {
|
||||
const fileReader = new FileReader();
|
||||
const target = e.target;
|
||||
const inputFile = target.files[0];
|
||||
|
||||
fileReader.onload = () => {
|
||||
const keystore = fileReader.result;
|
||||
const passReq = isPassRequired(keystore);
|
||||
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
file: keystore,
|
||||
valid: keystore.length && !passReq
|
||||
});
|
||||
};
|
||||
|
||||
fileReader.readAsText(inputFile, 'utf-8');
|
||||
};
|
||||
}
|
|
@ -1,10 +1,5 @@
|
|||
.LedgerDecrypt {
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
|
||||
&-decrypt {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-help {
|
||||
margin-top: 10px;
|
||||
|
@ -21,6 +16,16 @@
|
|||
}
|
||||
|
||||
&-buy {
|
||||
margin-top: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.Spinner {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
import './LedgerNano.scss';
|
||||
import React, { Component } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||
import { DeterministicWalletsModal } from './DeterministicWalletsModal';
|
||||
import { LedgerWallet } from 'libs/wallet';
|
||||
import Ledger3 from 'vendor/ledger3';
|
||||
import LedgerEth from 'vendor/ledger-eth';
|
||||
import DPATHS from 'config/dpaths';
|
||||
import { Spinner } from 'components/ui';
|
||||
|
||||
const DEFAULT_PATH = DPATHS.LEDGER[0].value;
|
||||
|
||||
|
@ -19,31 +20,74 @@ interface State {
|
|||
dPath: string;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
showTip: boolean;
|
||||
}
|
||||
|
||||
export default class LedgerNanoSDecrypt extends Component<Props, State> {
|
||||
export class LedgerNanoSDecrypt extends Component<Props, State> {
|
||||
public state: State = {
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: DEFAULT_PATH,
|
||||
error: null,
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
showTip: false
|
||||
};
|
||||
|
||||
public showTip = () => {
|
||||
this.setState({
|
||||
showTip: true
|
||||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
|
||||
const { dPath, publicKey, chainCode, error, isLoading, showTip } = this.state;
|
||||
const showErr = error ? 'is-showing' : '';
|
||||
|
||||
if (window.location.protocol !== 'https:') {
|
||||
return (
|
||||
<div className="LedgerDecrypt">
|
||||
<div className="alert alert-danger">
|
||||
Unlocking a Ledger hardware wallet is only possible on pages served over HTTPS. You can
|
||||
unlock your wallet at <a href="https://myetherwallet.com">MyEtherWallet.com</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="LedgerDecrypt col-md-4 col-sm-6">
|
||||
<div className="LedgerDecrypt">
|
||||
{showTip && (
|
||||
<p>
|
||||
<strong>Tip: </strong>Make sure you're logged into the ethereum app on your hardware
|
||||
wallet
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
className="LedgerDecrypt-decrypt btn btn-primary btn-lg"
|
||||
className="LedgerDecrypt-decrypt btn btn-primary btn-lg btn-block"
|
||||
onClick={this.handleNullConnect}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Unlocking...' : translate('ADD_Ledger_scan')}
|
||||
{isLoading ? (
|
||||
<div className="LedgerDecrypt-message">
|
||||
<Spinner light={true} />
|
||||
Unlocking...
|
||||
</div>
|
||||
) : (
|
||||
translate('ADD_Ledger_scan')
|
||||
)}
|
||||
</button>
|
||||
|
||||
<a
|
||||
className="LedgerDecrypt-buy btn btn-sm btn-default"
|
||||
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{translate('Don’t have a Ledger? Order one now!')}
|
||||
</a>
|
||||
|
||||
<div className={`LedgerDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
|
||||
|
||||
<div className="LedgerDecrypt-help">
|
||||
Guides:
|
||||
<div>
|
||||
|
@ -66,17 +110,6 @@ export default class LedgerNanoSDecrypt extends Component<Props, State> {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`LedgerDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
|
||||
|
||||
<a
|
||||
className="LedgerDecrypt-buy btn btn-sm btn-default"
|
||||
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{translate('Don’t have a Ledger? Order one now!')}
|
||||
</a>
|
||||
|
||||
<DeterministicWalletsModal
|
||||
isOpen={!!publicKey && !!chainCode}
|
||||
publicKey={publicKey}
|
||||
|
@ -88,7 +121,7 @@ export default class LedgerNanoSDecrypt extends Component<Props, State> {
|
|||
onPathChange={this.handlePathChange}
|
||||
walletType={translateRaw('x_Ledger')}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -99,7 +132,8 @@ export default class LedgerNanoSDecrypt extends Component<Props, State> {
|
|||
private handleConnect = (dPath: string = this.state.dPath) => {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: null
|
||||
error: null,
|
||||
showTip: false
|
||||
});
|
||||
|
||||
const ledger = new Ledger3('w0w');
|
||||
|
@ -109,6 +143,9 @@ export default class LedgerNanoSDecrypt extends Component<Props, State> {
|
|||
dPath,
|
||||
(res, err) => {
|
||||
if (err) {
|
||||
if (err.errorCode === 5) {
|
||||
this.showTip();
|
||||
}
|
||||
err = ethApp.getError(err);
|
||||
}
|
||||
|