Merge branch 'develop' into new-networks

This commit is contained in:
Daniel Ternyak 2018-03-08 13:51:51 -06:00 committed by GitHub
commit a5ce56ba5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 2571 additions and 877 deletions

View File

@ -29,7 +29,7 @@ export function loadBityRatesSucceededSwap(
export type TLoadShapeshiftRatesSucceededSwap = typeof loadShapeshiftRatesSucceededSwap;
export function loadShapeshiftRatesSucceededSwap(
payload
payload: interfaces.LoadShapeshiftRatesSucceededSwapAction['payload']
): interfaces.LoadShapeshiftRatesSucceededSwapAction {
return {
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED,

View File

@ -29,10 +29,14 @@ const repOptions = {
name: 'Augur'
};
export interface MappedRates {
[key: string]: any;
}
export function getAllRates() {
const mappedRates = {};
const mappedRates: MappedRates = {};
return _getAllRates().then(bityRates => {
bityRates.objects.forEach(each => {
bityRates.objects.forEach((each: any) => {
const pairName = each.pair;
const from = { id: pairName.substring(0, 3) };
const to = { id: pairName.substring(3, 6) };

View File

@ -1,4 +1,5 @@
import { checkHttpStatus, parseJSON } from './utils';
import { Omit } from 'react-redux';
const MAX_GAS_FAST = 250;
@ -21,15 +22,29 @@ export interface GasEstimates {
isDefault: boolean;
}
interface GasExpressResponse {
block_time: number;
blockNum: number;
fast: number;
fastest: number;
safeLow: number;
standard: number;
}
export function fetchGasEstimates(): Promise<GasEstimates> {
return fetch('https://dev.blockscale.net/api/gasexpress.json', {
mode: 'cors'
})
.then(checkHttpStatus)
.then(parseJSON)
.then((res: object) => {
.then((res: GasExpressResponse) => {
// Make sure it looks like a raw gas estimate, and it has valid values
const keys = ['safeLow', 'standard', 'fast', 'fastest'];
const keys: (keyof Omit<GasExpressResponse, 'block_time' | 'blockNum'>)[] = [
'safeLow',
'standard',
'fast',
'fastest'
];
keys.forEach(key => {
if (typeof res[key] !== 'number') {
throw new Error(

View File

@ -28,6 +28,44 @@ export const SHAPESHIFT_TOKEN_WHITELIST = [
];
export const SHAPESHIFT_WHITELIST = [...SHAPESHIFT_TOKEN_WHITELIST, 'ETH', 'ETC', 'BTC'];
interface IPairData {
limit: number;
maxLimit: number;
min: number;
minerFee: number;
pair: string;
rate: string;
}
interface IExtraPairData {
status: string;
image: string;
name: string;
}
interface IAvailablePairData {
[pairName: string]: IExtraPairData;
}
interface ShapeshiftMarketInfo {
rate: string;
limit: number;
pair: string;
maxLimit: number;
min: number;
minerFee: number;
}
interface TokenMap {
[pairName: string]: {
id: string;
rate: string;
limit: number;
min: number;
options: (IExtraPairData & { id: string })[];
};
}
class ShapeshiftService {
public whitelist = SHAPESHIFT_WHITELIST;
private url = SHAPESHIFT_BASE_URL;
@ -36,13 +74,18 @@ class ShapeshiftService {
'Content-Type': 'application/json'
};
public checkStatus(address) {
public checkStatus(address: string) {
return fetch(`${this.url}/txStat/${address}`)
.then(checkHttpStatus)
.then(parseJSON);
}
public sendAmount(withdrawal, originKind, destinationKind, destinationAmount) {
public sendAmount(
withdrawal: string,
originKind: string,
destinationKind: string,
destinationAmount: number
) {
const pair = `${originKind.toLowerCase()}_${destinationKind.toLowerCase()}`;
return fetch(`${this.url}/sendamount`, {
@ -81,7 +124,7 @@ class ShapeshiftService {
return mappedRates;
};
private getPairRates(marketInfo) {
private getPairRates(marketInfo: ShapeshiftMarketInfo[]) {
const filteredMarketInfo = marketInfo.filter(obj => {
const { pair } = obj;
const pairArr = pair.split('_');
@ -97,7 +140,7 @@ class ShapeshiftService {
return pairRates;
}
private async checkAvl(pairRates) {
private async checkAvl(pairRates: IPairData[]) {
const avlCoins = await this.getAvlCoins();
const mapAvl = pairRates.map(p => {
const { pair } = p;
@ -121,7 +164,8 @@ class ShapeshiftService {
};
}
});
return mapAvl;
const filered = mapAvl.filter(v => v);
return filered as (IPairData & IAvailablePairData)[];
}
private getAvlCoins() {
@ -130,7 +174,7 @@ class ShapeshiftService {
.then(parseJSON);
}
private getSinglePairRate(pair) {
private getSinglePairRate(pair: string) {
return fetch(`${this.url}/rate/${pair}`)
.then(checkHttpStatus)
.then(parseJSON);
@ -142,12 +186,12 @@ class ShapeshiftService {
.then(parseJSON);
}
private isWhitelisted(coin) {
private isWhitelisted(coin: string) {
return this.whitelist.includes(coin);
}
private mapMarketInfo(marketInfo) {
const tokenMap = {};
private mapMarketInfo(marketInfo: (IPairData & IAvailablePairData)[]) {
const tokenMap: TokenMap = {};
marketInfo.forEach(m => {
const originKind = m.pair.substring(0, 3);
const destinationKind = m.pair.substring(4, 7);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81px" height="120px" viewBox="0 0 81 120" 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>Artboard</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="100.099854%" y1="-16.122731%" x2="44.1579795%" y2="77.6511455%" id="linearGradient-1">
<stop stop-color="#2B415B" offset="13.45%"></stop>
<stop stop-color="#3B5676" offset="37.62%"></stop>
<stop stop-color="#54769E" offset="69.23%"></stop>
<stop stop-color="#52749B" offset="79.01%"></stop>
<stop stop-color="#4D6C92" offset="86.14%"></stop>
<stop stop-color="#436082" offset="92.44%"></stop>
<stop stop-color="#364F6C" offset="98.22%"></stop>
<stop stop-color="#314863" offset="100%"></stop>
</linearGradient>
<linearGradient x1="99.7887646%" y1="61.4941149%" x2="-4.60729499%" y2="13.3460499%" id="linearGradient-2">
<stop stop-color="#54769E" offset="0%"></stop>
<stop stop-color="#53749C" offset="48.02%"></stop>
<stop stop-color="#4F6F95" offset="68.78%"></stop>
<stop stop-color="#486588" offset="84.23%"></stop>
<stop stop-color="#435F80" offset="90.95%"></stop>
</linearGradient>
<linearGradient x1="725.856983%" y1="50.0229457%" x2="-347.124022%" y2="50.0229457%" id="linearGradient-3">
<stop stop-color="#20344C" offset="25.39%"></stop>
<stop stop-color="#273D57" offset="40.72%"></stop>
<stop stop-color="#395373" offset="67.33%"></stop>
<stop stop-color="#54769E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-653.788268%" y1="50.0214562%" x2="455.494413%" y2="50.0214562%" id="linearGradient-4">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.33%"></stop>
<stop stop-color="#3C5777" offset="68.97%"></stop>
<stop stop-color="#233850" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50.0370086%" y1="-209.335792%" x2="49.9575834%" y2="235.965027%" id="linearGradient-5">
<stop stop-color="#54769E" offset="0.6545247%"></stop>
<stop stop-color="#507198" offset="19.93%"></stop>
<stop stop-color="#466488" offset="45.02%"></stop>
<stop stop-color="#354F6D" offset="73.18%"></stop>
<stop stop-color="#21354D" offset="100%"></stop>
</linearGradient>
<linearGradient x1="163.859644%" y1="-95.5767241%" x2="-53.1396713%" y2="173.049483%" id="linearGradient-6">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.02%"></stop>
<stop stop-color="#3C5777" offset="68.13%"></stop>
<stop stop-color="#22364E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-44.1116757%" y1="-60.1463542%" x2="137.687762%" y2="146.957292%" id="linearGradient-7">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.02%"></stop>
<stop stop-color="#3C5777" offset="68.13%"></stop>
<stop stop-color="#22364E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-1.43584837%" y1="31.6007047%" x2="127.701745%" y2="103.798097%" id="linearGradient-8">
<stop stop-color="#54769E" offset="26.64%"></stop>
<stop stop-color="#425E7F" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-71.276129%" y1="-65.3137535%" x2="149.174581%" y2="96.3311507%" id="linearGradient-9">
<stop stop-color="#54769E" stop-opacity="0" offset="46.09%"></stop>
<stop stop-color="#52739A" stop-opacity="0.2156" offset="56.99%"></stop>
<stop stop-color="#4A698E" stop-opacity="0.4266" offset="67.64%"></stop>
<stop stop-color="#3D597B" stop-opacity="0.6356" offset="78.2%"></stop>
<stop stop-color="#2C435F" stop-opacity="0.8422" offset="88.63%"></stop>
<stop stop-color="#1B2E45" offset="96.61%"></stop>
</linearGradient>
<linearGradient x1="50.309375%" y1="295.997443%" x2="50.309375%" y2="-124.649242%" id="linearGradient-10">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.02%"></stop>
<stop stop-color="#3C5777" offset="68.13%"></stop>
<stop stop-color="#22364E" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" fill-rule="nonzero">
<g id="logo-shapeshift">
<polygon id="Shape" fill="#273C51" points="75.9045553 29.0113736 81 0.0349956255 59.7045553 8.88888889 36.3006508 8.88888889 15.0052061 0 20.1357918 29.0113736 15.3214751 45.1793526 19.8546638 48.0139983 0 65.3718285 0 81.67979 22.7010846 112.825897 44.1019523 119.965004 44.1370933 120 60.8642082 111.461067 60.8993492 111.426072 60.8993492 98.7926509 51.4112798 93.6132983 51.4112798 93.6132983 51.4112798 93.6132983 66.3110629 54.1032371 66.2056399 54.1032371 80.683731 45.1793526"></polygon>
<g id="Group">
<polygon id="Shape" fill="url(#linearGradient-1)" points="34.2584416 35.3372093 0 65.2325581 47.7233766 92.5465116 47.8987013 36.244186"></polygon>
<polygon id="Shape" fill="#466284" points="29.3844156 53.8255814 47.7584416 102.697674 47.8987013 56.5465116"></polygon>
<polygon id="Shape" fill="#354D6A" points="66.1675325 54 47.7584416 102.697674 47.8987013 56.5465116"></polygon>
<polygon id="Shape" fill="url(#linearGradient-2)" points="80.8246753 0.104651163 62.5558442 9.6627907 47.9337662 12.8372093 33.1714286 9.6627907 14.9727273 0.0697674419 21.2493506 28.9186047 15.2883117 45.1046512 36.187013 58.0116279 47.7935065 70.5697674 47.7935065 70.6744186 47.8636364 70.6046512 47.9337662 70.6744186 47.9337662 70.5697674 59.5402597 58.0116279 80.5090909 45.1046512 74.5480519 28.9534884"></polygon>
<polygon id="Shape" fill="url(#linearGradient-3)" points="80.8246753 0.104651163 75.7402597 28.9883721 80.5090909 45.1046512 74.5480519 28.9534884"></polygon>
<polygon id="Shape" fill="url(#linearGradient-4)" points="14.9727273 0.0697674419 20.0922078 28.9883721 15.2883117 45.1046512 21.2493506 28.9186047"></polygon>
<polygon id="Shape" fill="url(#linearGradient-5)" points="14.9727273 0.0697674419 36.2220779 8.93023256 59.5753247 8.93023256 80.8246753 0.104651163 62.5558442 9.6627907 47.9337662 12.8372093 33.1714286 9.6627907"></polygon>
<polygon id="Shape" fill="url(#linearGradient-6)" points="21.2493506 28.9186047 20.1974026 31.8139535 42.2532468 11.5813953"></polygon>
<polygon id="Shape" fill="url(#linearGradient-7)" points="74.5480519 28.9534884 53.6493506 11.5813953 75.5649351 31.6744186"></polygon>
<polygon id="Shape" fill="url(#linearGradient-8)" points="0 65.2325581 51.3 93.3837209 57.787013 109.534884 44.0415584 114.732558 22.6519481 112.534884 0 81.4883721"></polygon>
<polygon id="Shape" fill="#FFFFFF" points="42.4636364 88.5348837 22.7220779 112.465116 22.6519481 112.534884 44.0064935 119.651163 44.0415584 119.686047 60.7324675 111.174419 60.7675325 111.139535 60.7675325 98.5465116"></polygon>
<polygon id="Shape" fill="url(#linearGradient-9)" points="74.5480519 28.9534884 80.8246753 0.104651163 62.5558442 9.6627907 53.6493506 11.5813953"></polygon>
<polygon id="Shape" fill="#FFFFFF" points="47.8987013 70.6744186 42.112987 64.3604651 47.8987013 58.0116279 53.7194805 64.3604651"></polygon>
<polygon id="Shape" fill="url(#linearGradient-10)" points="47.3376623 12.6976744 47.9337662 12.8372093 48.4597403 12.7325581 47.8987013 49.5348837"></polygon>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -1,36 +1,35 @@
// Mixins
// --------------------------------------------------
// Utilities
@import "mixins/hide-text.less";
@import "mixins/opacity.less";
@import "mixins/image.less";
@import "mixins/labels.less";
@import "mixins/reset-filter.less";
@import "mixins/resize.less";
@import "mixins/responsive-visibility.less";
@import "mixins/size.less";
@import "mixins/tab-focus.less";
@import "mixins/reset-text.less";
@import "mixins/text-emphasis.less";
@import "mixins/text-overflow.less";
@import "mixins/vendor-prefixes.less";
@import 'mixins/hide-text.less';
@import 'mixins/opacity.less';
@import 'mixins/image.less';
@import 'mixins/labels.less';
@import 'mixins/reset-filter.less';
@import 'mixins/resize.less';
@import 'mixins/responsive-visibility.less';
@import 'mixins/size.less';
@import 'mixins/reset-text.less';
@import 'mixins/text-emphasis.less';
@import 'mixins/text-overflow.less';
@import 'mixins/vendor-prefixes.less';
// Components
@import "mixins/alerts.less";
@import "mixins/buttons.less";
@import "mixins/panels.less";
@import "mixins/pagination.less";
@import "mixins/list-group.less";
@import "mixins/nav-divider.less";
@import "mixins/forms.less";
@import "mixins/progress-bar.less";
@import "mixins/table-row.less";
@import 'mixins/alerts.less';
@import 'mixins/buttons.less';
@import 'mixins/panels.less';
@import 'mixins/pagination.less';
@import 'mixins/list-group.less';
@import 'mixins/nav-divider.less';
@import 'mixins/forms.less';
@import 'mixins/progress-bar.less';
@import 'mixins/table-row.less';
// Skins
@import "mixins/background-variant.less";
@import "mixins/border-radius.less";
@import "mixins/gradients.less";
@import 'mixins/background-variant.less';
@import 'mixins/border-radius.less';
@import 'mixins/gradients.less';
// Layout
@import "mixins/clearfix.less";
@import "mixins/center-block.less";
@import "mixins/nav-vertical-align.less";
@import "mixins/grid-framework.less";
@import "mixins/grid.less";
@import 'mixins/clearfix.less';
@import 'mixins/center-block.less';
@import 'mixins/nav-vertical-align.less';
@import 'mixins/grid-framework.less';
@import 'mixins/grid.less';

View File

@ -20,10 +20,10 @@
// Set the border and box shadow on specific inputs to match
.form-control {
border-color: @border-color;
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); // Redeclare so transitions work
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.075)); // Redeclare so transitions work
&:focus {
border-color: darken(@border-color, 10%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 3px rgba(@brand-primary, .5);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 3px rgba(@brand-primary, 0.5);
}
}
// Set validation states also for addons
@ -51,11 +51,10 @@
// Example usage: change the default blue border and shadow to white for better
// contrast against a dark gray background.
.form-control-focus(@color: @input-border-focus) {
@color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
@color-rgba: rgba(red(@color), green(@color), blue(@color), 0.6);
&:focus {
border-color: @color;
outline: 0;
.box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
.box-shadow(~'inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}');
}
}

View File

@ -1,6 +0,0 @@
// WebKit-style focus
.tab-focus() {
outline: thin dotted;
outline-offset: 3px;
}

View File

@ -7,10 +7,6 @@
position: absolute;
left: 5px;
top: 5px;
&:hover,
&:active {
outline: 0;
}
}
}

View File

@ -18,7 +18,7 @@ export const AmountField: React.SFC<Props> = ({
<AmountFieldFactory
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group input-group-inline-dropdown">
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('SEND_amount')}</div>
<Input
className={`input-group-input ${
@ -37,5 +37,8 @@ export const AmountField: React.SFC<Props> = ({
/>
);
const isAmountValid = (raw, customValidator, isValid) =>
customValidator ? customValidator(raw) : isValid;
const isAmountValid = (
raw: string,
customValidator: ((rawAmount: string) => boolean) | undefined,
isValid: boolean
) => (customValidator ? customValidator(raw) : isValid);

View File

@ -8,7 +8,6 @@ import { chain, flatMap } from 'lodash';
import { TokenBalance, getShownTokenBalances } from 'selectors/wallet';
import { Balance } from 'libs/wallet';
import './EquivalentValues.scss';
import { Wei } from 'libs/units';
import { AppState } from 'reducers';
import { getNetworkConfig, getOffline } from 'selectors/config';
import { connect } from 'react-redux';
@ -16,6 +15,7 @@ import btcIco from 'assets/images/bitcoin.png';
import ethIco from 'assets/images/ether.png';
import repIco from 'assets/images/augur.png';
import { NetworkConfig } from 'types/network';
import BN from 'bn.js';
interface AllValue {
symbol: string;
@ -51,6 +51,14 @@ interface DispatchProps {
fetchCCRates: TFetchCCRatesRequested;
}
interface FiatSymbols {
[key: string]: string;
}
interface Rates {
[rate: string]: number;
}
type Props = StateProps & DispatchProps;
class EquivalentValues extends React.Component<Props, State> {
@ -110,7 +118,7 @@ class EquivalentValues extends React.Component<Props, State> {
}
}
public selectOption = equivalentValues => {
public selectOption = (equivalentValues: Option) => {
this.setState({ equivalentValues });
};
@ -120,28 +128,38 @@ class EquivalentValues extends React.Component<Props, State> {
const isFetching =
!balance || balance.isPending || !tokenBalances || Object.keys(rates).length === 0;
const pairRates = this.generateValues(equivalentValues.label, equivalentValues.value);
const fiatSymbols = {
const fiatSymbols: FiatSymbols = {
USD: '$',
EUR: '€',
GBP: '£',
CHF: ' '
};
const coinAndTokenSymbols = {
const coinAndTokenSymbols: any = {
BTC: btcIco,
ETH: ethIco,
REP: repIco
};
interface ValueProps {
className: string;
rate: string;
value: BN | null;
symbol?: string;
icon?: string;
key?: number | string;
}
const Value = ({ className = '', rate, value, symbol = '', icon = '' }) => (
<div className={`EquivalentValues-values-currency ${className}`}>
<img src={icon} />
{!!symbol && <span className="EquivalentValues-values-currency-fiat-symbol">{symbol}</span>}
<span className="EquivalentValues-values-currency-label">{rate}</span>{' '}
const Value = (props: ValueProps) => (
<div className={`EquivalentValues-values-currency ${props.className}`}>
<img src={props.icon} />
{!!props.symbol && (
<span className="EquivalentValues-values-currency-fiat-symbol">{props.symbol}</span>
)}
<span className="EquivalentValues-values-currency-label">{props.rate}</span>{' '}
<span className="EquivalentValues-values-currency-value">
<UnitDisplay
unit={'ether'}
value={value}
displayShortBalance={rateSymbols.isFiat(rate) ? 2 : 3}
value={props.value}
displayShortBalance={rateSymbols.isFiat(props.rate) ? 2 : 3}
checkOffline={true}
/>
</span>
@ -157,7 +175,7 @@ class EquivalentValues extends React.Component<Props, State> {
// TODO: Update type
value={equivalentValues as any}
options={options as any}
onChange={this.selectOption}
onChange={this.selectOption as any}
clearable={false}
searchable={false}
/>
@ -224,7 +242,7 @@ class EquivalentValues extends React.Component<Props, State> {
const allRates = Object.values(balance).map(
value => !!rates[value.symbol] && rates[value.symbol]
);
const allEquivalentValues = allRates.map((rateType, i) => {
const allEquivalentValues = allRates.map((rateType: any, i) => {
return {
symbol: Object.keys(rates)[i],
equivalentValues: [
@ -260,9 +278,9 @@ class EquivalentValues extends React.Component<Props, State> {
// return equivalent value (unit * rate * balance)
private handleValues(unit: string, balance: Balance['wei']) {
const { rates } = this.props;
const ratesObj = { ...rates[unit] };
const ratesObj: Rates = { ...rates[unit] };
return Object.keys(ratesObj).map(key => {
const value = (balance as Wei).muln(ratesObj[key]);
const value = balance!.muln(ratesObj[key]);
return { rate: key, value };
});
}

View File

@ -2,10 +2,14 @@ import React from 'react';
import CoinbaseLogo from 'assets/images/logo-coinbase.svg';
import { NewTabLink } from 'components/ui';
export const Coinbase: React.SFC = () => (
interface Props {
address: string;
}
export const Coinbase: React.SFC<Props> = ({ address }) => (
<NewTabLink
className="Promos-promo Promos--coinbase"
href="https://buy.coinbase.com?code=60c05061-3a76-57be-b1cd-a7afa97bcb8c&address=0xA7DeFf12461661212734dB35AdE9aE7d987D648c&crypto_currency=ETH&currency=USD"
href={`https://buy.coinbase.com?code=60c05061-3a76-57be-b1cd-a7afa97bcb8c&address=${address}&crypto_currency=ETH&currency=USD`}
>
<div className="Promos-promo-inner">
<div className="Promos-promo-text">
@ -13,7 +17,7 @@ export const Coinbase: React.SFC = () => (
<h5 key="2">Buy ETH with USD</h5>
</div>
<div className="Promos-promo-images">
<img src={CoinbaseLogo} />
<img src={CoinbaseLogo} alt="Coinbase logo" />
</div>
</div>
</NewTabLink>

View File

@ -11,8 +11,8 @@ export const HardwareWallets: React.SFC = () => (
<h6>Learn more about protecting your funds.</h6>
</div>
<div className="Promos-promo-images">
<img src={ledgerLogo} />
<img src={trezorLogo} />
<img src={ledgerLogo} alt="Ledger Logo" />
<img src={trezorLogo} alt="Trezor Logo" />
</div>
</div>
</HelpLink>

View File

@ -13,7 +13,7 @@ export const Shapeshift: React.SFC = () => (
</h5>
</div>
<div className="Promos-promo-images">
<img src={ShapeshiftLogo} />
<img src={ShapeshiftLogo} alt="Shapeshift Logo" />
</div>
</div>
</Link>

View File

@ -85,7 +85,6 @@
height: 12px;
border: 3px solid $gray-lightest;
border-radius: 100%;
outline: none;
opacity: 0.6;
&.is-active {
@ -96,7 +95,7 @@
// Per-promo customizations
&--shapeshift {
background-color: #263A52;
background-color: #263a52;
.Promos-promo-images {
max-width: 130px;

View File

@ -2,21 +2,29 @@ import React from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { HardwareWallets, Coinbase, Shapeshift } from './PromoComponents';
import './Promos.scss';
import { connect } from 'react-redux';
import { AppState } from '../../reducers';
const promos = [HardwareWallets, Coinbase, Shapeshift];
const CarouselAnimation = ({ children, ...props }) => (
const CarouselAnimation = ({ children, ...props }: any) => (
<CSSTransition {...props} timeout={300} classNames="carousel">
{children}
</CSSTransition>
);
// Don't change Coinbase index
const promos = [HardwareWallets, Coinbase, Shapeshift];
interface State {
activePromo: number;
}
export default class Promos extends React.PureComponent<{}, State> {
interface StateProps {
wallet: AppState['wallet']['inst'];
}
class PromosClass extends React.PureComponent<StateProps, State> {
public timer: any = null;
public promos = [HardwareWallets, Coinbase, Shapeshift];
public state = {
activePromo: parseInt(String(Math.random() * promos.length), 10)
@ -30,13 +38,27 @@ export default class Promos extends React.PureComponent<{}, State> {
clearInterval(this.timer);
}
public getPromo() {
const { activePromo } = this.state;
const { wallet } = this.props;
if (activePromo === 1) {
if (wallet) {
return <Coinbase address={wallet.getAddressString()} />;
} else {
return <Shapeshift />;
}
} else {
return promos[activePromo];
}
}
public render() {
const { activePromo } = this.state;
return (
<div className="Promos">
<TransitionGroup className="Promos-promo-wrapper">
<CarouselAnimation key={Math.random()}>{promos[activePromo]}</CarouselAnimation>
<CarouselAnimation key={Math.random()}>{this.getPromo()}</CarouselAnimation>
</TransitionGroup>
<div className="Promos-nav">
{promos.map((_, index) => {
@ -64,3 +86,11 @@ export default class Promos extends React.PureComponent<{}, State> {
this.setState({ activePromo });
};
}
function mapStateToProps(state: AppState): StateProps {
return {
wallet: state.wallet.inst
};
}
export default connect(mapStateToProps, {})(PromosClass);

View File

@ -66,11 +66,11 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
{fields.map(field => {
return (
<label className="AddCustom-field form-group" key={field.name}>
<span className="AddCustom-field-label">{field.label}</span>
<div className="input-group-header">{field.label}</div>
<Input
className={`${
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
} AddCustom-field-input input-sm`}
} input-group-input-small`}
type="text"
name={field.name}
value={field.value}

View File

@ -15,19 +15,23 @@ interface Props {
onRemoveCustomToken(symbol: string): any;
}
interface TrackedTokens {
[symbol: string]: boolean;
}
interface State {
trackedTokens: { [symbol: string]: boolean };
showCustomTokenForm: boolean;
}
export default class TokenBalances extends React.PureComponent<Props, State> {
public state = {
public state: State = {
trackedTokens: {},
showCustomTokenForm: false
};
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.tokenBalances !== this.props.tokenBalances) {
const trackedTokens = nextProps.tokenBalances.reduce((prev, t) => {
const trackedTokens = nextProps.tokenBalances.reduce<TrackedTokens>((prev, t) => {
prev[t.symbol] = !t.balance.isZero();
return prev;
}, {});

View File

@ -54,6 +54,7 @@ export default class TokenRow extends React.PureComponent<Props, State> {
{!!custom && (
<img
src={removeIcon}
alt="Remove"
className="TokenRow-symbol-remove"
title="Remove Token"
onClick={this.onRemove}

View File

@ -1,5 +1,5 @@
@import "common/sass/variables";
@import "common/sass/mixins";
@import 'common/sass/variables';
@import 'common/sass/mixins';
.BetaAgreement {
@include cover-message;
@ -20,7 +20,6 @@
margin: 0 auto;
border: none;
padding: 0;
outline: none;
transition: $transition;
&.is-continue {

View File

@ -8,6 +8,7 @@ import { connect } from 'react-redux';
import { SerializedTransaction } from 'components/renderCbs';
import { AppState } from 'reducers';
import { getFrom, getUnit, isEtherTransaction } from 'selectors/transaction';
import { toChecksumAddress } from 'ethereumjs-util';
interface StateProps {
from: AppState['transaction']['meta']['from'];
@ -24,7 +25,9 @@ class AddressesClass extends Component<StateProps> {
return (
<SerializedTransaction
withSerializedTransaction={(_, { to, data }) => {
const toFormatted = isToken ? ERC20.transfer.decodeInput(data)._to : to;
const toFormatted = toChecksumAddress(
isToken ? ERC20.transfer.decodeInput(data)._to : to
);
return (
<div className="tx-modal-address">
<div className="tx-modal-address-from">

View File

@ -13,7 +13,7 @@ interface StateProps {
}
interface OwnProps {
withProps(props: CallBackProps);
withProps(props: CallBackProps): null | React.ReactElement<any>;
onChange(value: React.FormEvent<HTMLInputElement>): void;
}

View File

@ -41,9 +41,9 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
}
}
public componentWillReceiveProps(nextProps) {
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.privateKey !== this.props.privateKey) {
this.setState({ privateKey: nextProps.privateKey });
this.setState({ privateKey: nextProps.privateKey || '' });
}
}
@ -55,13 +55,13 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
return (
<Modal
title={translate('Generate Keystore File')}
title={translateRaw('Generate Keystore File')}
isOpen={this.props.isOpen}
handleClose={this.handleClose}
>
<form className="GenKeystore" onSubmit={this.handleSubmit}>
<div className="input-group-wrapper GenKeystore-field">
<label className="input-group input-group-inline-dropdown">
<label className="input-group input-group-inline">
<div className="input-group-header">Private Key</div>
<TogglablePassword
name="privateKey"
@ -74,7 +74,7 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
</label>
</div>
<div className="input-group-wrapper GenKeystore-field">
<label className="input-group input-group-inline-dropdown">
<label className="input-group input-group-inline">
<div className="input-group-header">Password</div>
<TogglablePassword
name="password"

View File

@ -0,0 +1,15 @@
.CustomNodeModal {
.flex-wrapper {
margin: 0px -8px;
> .input-group {
margin: 0px 8px;
> .input-group-input {
width: 100%;
}
}
}
input[type='checkbox'] {
margin-right: 1rem;
margin-bottom: 1rem;
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import Modal, { IButton } from 'components/ui/Modal';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import { CustomNetworkConfig } from 'types/network';
import { CustomNodeConfig } from 'types/node';
import { TAddCustomNetwork, addCustomNetwork, AddCustomNodeAction } from 'actions/config';
@ -13,19 +13,13 @@ import {
} from 'selectors/config';
import { CustomNode } from 'libs/nodes';
import { Input } from 'components/ui';
import Dropdown from 'components/ui/Dropdown';
import './CustomNodeModal.scss';
const CUSTOM = 'custom';
interface InputProps {
name: string;
placeholder?: string;
type?: string;
autoComplete?: 'off';
onFocus?(): void;
onBlur?(): void;
}
const CUSTOM = { label: 'Custom', value: 'custom' };
interface OwnProps {
isOpen: boolean;
addCustomNode(payload: AddCustomNodeAction['payload']): void;
handleClose(): void;
}
@ -55,7 +49,7 @@ interface State {
type Props = OwnProps & StateProps & DispatchProps;
class CustomNodeModal extends React.Component<Props, State> {
public state: State = {
public INITIAL_STATE = {
name: '',
url: '',
network: Object.keys(this.props.staticNetworks)[0],
@ -66,9 +60,17 @@ class CustomNodeModal extends React.Component<Props, State> {
username: '',
password: ''
};
public state: State = this.INITIAL_STATE;
public componentDidUpdate(prevProps: Props) {
// Reset state when modal opens
if (!prevProps.isOpen && prevProps.isOpen !== this.props.isOpen) {
this.setState(this.INITIAL_STATE);
}
}
public render() {
const { customNetworks, handleClose, staticNetworks } = this.props;
const { customNetworks, handleClose, staticNetworks, isOpen } = this.props;
const { network } = this.state;
const isHttps = window.location.protocol.includes('https');
const invalids = this.getInvalids();
@ -88,158 +90,152 @@ class CustomNodeModal extends React.Component<Props, State> {
];
const conflictedNode = this.getConflictedNode();
const staticNetwrks = Object.keys(staticNetworks).map(net => {
return { label: net, value: net };
});
const customNetwrks = Object.entries(customNetworks).map(([id, net]) => {
return { label: net.name + ' (Custom)', value: id };
});
const options = [...staticNetwrks, ...customNetwrks, CUSTOM];
return (
<Modal
title={translate('NODE_Title')}
isOpen={true}
title={translateRaw('NODE_Title')}
isOpen={isOpen}
buttons={buttons}
handleClose={handleClose}
maxWidth={580}
>
<div>
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
{conflictedNode && (
<div className="alert alert-warning small">
You already have a node called '{conflictedNode.name}' that matches this one, saving
this will overwrite it
{conflictedNode && (
<div className="alert alert-warning small">
You already have a node called '{conflictedNode.name}' that matches this one, saving
this will overwrite it
</div>
)}
<form className="CustomNodeModal">
<div className="flex-wrapper">
<label className="col-sm-9 input-group flex-grow-1">
<div className="input-group-header">Node Name</div>
<Input
className={`input-group-input ${this.state.name && invalids.name ? 'invalid' : ''}`}
type="text"
placeholder="My Node"
value={this.state.name}
onChange={e => this.setState({ name: e.currentTarget.value })}
/>
</label>
<label className="col-sm-3 input-group">
<div className="input-group-header">Network</div>
<Dropdown
className="input-group-dropdown"
value={network}
options={options}
clearable={false}
onChange={(e: { label: string; value: string }) =>
this.setState({ network: e.value })
}
/>
</label>
</div>
{network === CUSTOM.value && (
<div className="flex-wrapper">
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">Network Name</div>
<Input
className={`input-group-input ${
this.state.customNetworkId && invalids.customNetworkId ? 'invalid' : ''
}`}
type="text"
placeholder="My Custom Network"
value={this.state.customNetworkId}
onChange={e => this.setState({ customNetworkId: e.currentTarget.value })}
/>
</label>
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">Currency</div>
<Input
className={`input-group-input ${
this.state.customNetworkUnit && invalids.customNetworkUnit ? 'invalid' : ''
}`}
type="text"
placeholder="ETH"
value={this.state.customNetworkUnit}
onChange={e => this.setState({ customNetworkUnit: e.currentTarget.value })}
/>
</label>
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">Chain ID</div>
<Input
className={`input-group-input ${
this.state.customNetworkChainId && invalids.customNetworkChainId
? 'invalid'
: ''
}`}
type="text"
placeholder="1"
value={this.state.customNetworkChainId}
onChange={e => this.setState({ customNetworkChainId: e.currentTarget.value })}
/>
</label>
</div>
)}
<form>
<div className="row">
<div className="col-sm-7">
<label>{translate('NODE_Name')}</label>
{this.renderInput(
{
name: 'name',
placeholder: 'My Node'
},
invalids
)}
</div>
<div className="col-sm-5">
<label>Network</label>
<select
className="form-control"
name="network"
value={network}
onChange={this.handleChange}
>
{Object.keys(staticNetworks).map(net => (
<option key={net} value={net}>
{net}
</option>
))}
{Object.entries(customNetworks).map(([id, net]) => (
<option key={id} value={id}>
{net.name} (Custom)
</option>
))}
<option value={CUSTOM}>Custom...</option>
</select>
</div>
</div>
<label className="input-group input-group-inline">
<div className="input-group-header">URL</div>
<Input
className={`input-group-input ${this.state.url && invalids.url ? 'invalid' : ''}`}
type="text"
placeholder="https://127.0.0.1:8545/"
value={this.state.url}
onChange={e => this.setState({ url: e.currentTarget.value })}
autoComplete="off"
/>
</label>
{network === CUSTOM && (
<div className="row">
<div className="col-sm-6">
<label className="is-required">Network Name</label>
{this.renderInput(
{
name: 'customNetworkId',
placeholder: 'My Custom Network'
},
invalids
)}
</div>
<div className="col-sm-3">
<label className="is-required">Currency</label>
{this.renderInput(
{
name: 'customNetworkUnit',
placeholder: 'ETH'
},
invalids
)}
</div>
<div className="col-sm-3">
<label>Chain ID</label>
{this.renderInput(
{
name: 'customNetworkChainId',
placeholder: 'e.g. 1'
},
invalids
)}
</div>
</div>
)}
<label>
<input
type="checkbox"
name="hasAuth"
checked={this.state.hasAuth}
onChange={() => this.setState({ hasAuth: !this.state.hasAuth })}
/>
<span>HTTP Basic Authentication</span>
</label>
<div className="row">
<div className="col-sm-12">
<label>URL</label>
{this.renderInput(
{
name: 'url',
placeholder: 'e.g. https://127.0.0.1:8545/',
autoComplete: 'off'
},
invalids
)}
</div>
{this.state.hasAuth && (
<div className="flex-wrapper ">
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">Username</div>
<Input
className={`input-group-input ${
this.state.username && invalids.username ? 'invalid' : ''
}`}
type="text"
value={this.state.username}
onChange={e => this.setState({ username: e.currentTarget.value })}
/>
</label>
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">Password</div>
<Input
className={`input-group-input ${
this.state.password && invalids.password ? 'invalid' : ''
}`}
type="password"
value={this.state.password}
onChange={e => this.setState({ password: e.currentTarget.value })}
/>
</label>
</div>
<div className="row">
<div className="col-sm-12">
<label>
<input
type="checkbox"
name="hasAuth"
checked={this.state.hasAuth}
onChange={this.handleCheckbox}
/>{' '}
<span>HTTP Basic Authentication</span>
</label>
</div>
</div>
{this.state.hasAuth && (
<div className="row">
<div className="col-sm-6">
<label className="is-required">Username</label>
{this.renderInput({ name: 'username' }, invalids)}
</div>
<div className="col-sm-6">
<label className="is-required">Password</label>
{this.renderInput(
{
name: 'password',
type: 'password'
},
invalids
)}
</div>
</div>
)}
</form>
</div>
)}
</form>
</Modal>
);
}
private renderInput(input: InputProps, invalids: { [key: string]: boolean }) {
return (
<Input
className={`${this.state[input.name] && invalids[input.name] ? 'invalid' : ''}`}
value={this.state[input.name]}
onChange={this.handleChange}
autoComplete="off"
{...input}
/>
);
}
private getInvalids(): { [key: string]: boolean } {
const {
url,
@ -278,7 +274,7 @@ class CustomNodeModal extends React.Component<Props, State> {
}
// If they have a custom network, make sure info is provided
if (network === CUSTOM) {
if (network === CUSTOM.value) {
if (!customNetworkId) {
invalids.customNetworkId = true;
}
@ -315,7 +311,7 @@ class CustomNodeModal extends React.Component<Props, State> {
const { network, url, name, username, password } = this.state;
const networkId =
network === CUSTOM
network === CUSTOM.value
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
: network;
@ -348,20 +344,10 @@ class CustomNodeModal extends React.Component<Props, State> {
return customNodes[config.id];
}
private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = ev.currentTarget;
this.setState({ [name as any]: value });
};
private handleCheckbox = (ev: React.FormEvent<HTMLInputElement>) => {
const { name } = ev.currentTarget;
this.setState({ [name as any]: !this.state[name as keyof State] });
};
private saveAndAdd = () => {
const node = this.makeCustomNodeConfigFromState();
if (this.state.network === CUSTOM) {
if (this.state.network === CUSTOM.value) {
const network = this.makeCustomNetworkConfigFromState();
this.props.addCustomNetwork({ config: network, id: node.network });

View File

@ -51,7 +51,7 @@
margin-top: -.1rem;
width: 1.3rem;
height: 1.3rem;
background-image: url('~assets/images/swap.svg');
background-image: url('~assets/images/logo-shapeshift-no-text.svg');
background-position: center;
background-repeat: no-repeat;
background-size: contain;

View File

@ -192,12 +192,11 @@ class Header extends Component<Props, State> {
<Navigation color={!network.isCustom && network.color} />
{isAddingCustomNode && (
<CustomNodeModal
addCustomNode={this.addCustomNode}
handleClose={this.closeCustomNodeModal}
/>
)}
<CustomNodeModal
isOpen={isAddingCustomNode}
addCustomNode={this.addCustomNode}
handleClose={this.closeCustomNodeModal}
/>
</div>
);
}

View File

@ -18,7 +18,7 @@ interface State {
}
class LogOutPromptClass extends React.Component<Props, State> {
constructor(props) {
constructor(props: Props) {
super(props);
this.state = {
nextLocation: null,

View File

@ -100,8 +100,8 @@ export default class PaperWallet extends React.Component<Props, {}> {
return (
<div style={styles.container}>
<img src={sidebarImg} style={styles.sidebar} />
<img src={ethLogo} style={styles.ethLogo} />
<img src={sidebarImg} style={styles.sidebar} alt="MyCrypto Logo" />
<img src={ethLogo} style={styles.ethLogo} alt="ETH Logo" />
<div style={styles.block}>
<div style={styles.box}>
@ -111,7 +111,7 @@ export default class PaperWallet extends React.Component<Props, {}> {
</div>
<div style={styles.block}>
<img src={notesBg} style={styles.box} />
<img src={notesBg} style={styles.box} aria-hidden={true} />
<p style={styles.blockText}>AMOUNT / NOTES</p>
</div>

View File

@ -1,5 +1,5 @@
import React from 'react';
import Slider from 'rc-slider';
import Slider, { createSliderWithTooltip } from 'rc-slider';
import translate, { translateRaw } from 'translations';
import FeeSummary from './FeeSummary';
import './SimpleGas.scss';
@ -16,12 +16,13 @@ import { getEstimates, getIsEstimating } from 'selectors/gas';
import { Wei, fromWei } from 'libs/units';
import { gasPriceDefaults } from 'config';
import { InlineSpinner } from 'components/ui/InlineSpinner';
const SliderWithTooltip = Slider.createSliderWithTooltip(Slider);
import { TInputGasPrice } from 'actions/transaction';
const SliderWithTooltip = createSliderWithTooltip(Slider);
interface OwnProps {
gasPrice: AppState['transaction']['fields']['gasPrice'];
inputGasPrice(rawGas: string);
setGasPrice(rawGas: string);
setGasPrice: TInputGasPrice;
inputGasPrice(rawGas: string): void;
}
interface StateProps {
@ -39,14 +40,22 @@ interface ActionProps {
type Props = OwnProps & StateProps & ActionProps;
interface State {
hasSetRecommendedGasPrice: boolean;
}
class SimpleGas extends React.Component<Props> {
public state: State = {
hasSetRecommendedGasPrice: false
};
public componentDidMount() {
this.fixGasPrice();
this.props.fetchGasEstimates();
}
public componentWillReceiveProps(nextProps: Props) {
if (!this.props.gasEstimates && nextProps.gasEstimates) {
if (!this.state.hasSetRecommendedGasPrice && nextProps.gasEstimates) {
this.setState({ hasSetRecommendedGasPrice: true });
this.props.setGasPrice(nextProps.gasEstimates.fast.toString());
}
}
@ -120,21 +129,6 @@ class SimpleGas extends React.Component<Props> {
this.props.inputGasPrice(gasGwei.toString());
};
private fixGasPrice() {
const { gasPrice, gasEstimates } = this.props;
if (!gasEstimates) {
return;
}
// If the gas price is above or below our minimum, bring it in line
const gasPriceGwei = this.getGasPriceGwei(gasPrice.value);
if (gasPriceGwei < gasEstimates.safeLow) {
this.props.setGasPrice(gasEstimates.safeLow.toString());
} else if (gasPriceGwei > gasEstimates.fastest) {
this.props.setGasPrice(gasEstimates.fastest.toString());
}
}
private getGasPriceGwei(gasPriceValue: Wei) {
return parseFloat(fromWei(gasPriceValue, 'gwei'));
}

View File

@ -18,6 +18,7 @@ interface Props {
isValid?: boolean;
isVisible?: boolean;
validity?: 'valid' | 'invalid' | 'semivalid';
readOnly?: boolean;
// Textarea-only props
isTextareaWhenVisible?: boolean;
@ -61,18 +62,16 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
onChange,
onFocus,
onBlur,
handleToggleVisibility
handleToggleVisibility,
readOnly
} = this.props;
const { isVisible } = this.state;
const validClass = validity
? `is-${validity}`
: isValid === null || isValid === undefined ? '' : isValid ? 'is-valid' : 'is-invalid';
return (
<div className={`TogglablePassword input-group input-group-inline-dropdown ${className}`}>
<div className={`TogglablePassword input-group input-group-inline ${className}`}>
{isTextareaWhenVisible && isVisible ? (
<TextArea
className={validClass}
className={validity || !isValid ? 'invalid' : ''}
value={value}
name={name}
disabled={disabled}
@ -83,6 +82,7 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
placeholder={placeholder}
rows={this.props.rows || 3}
aria-label={ariaLabel}
readOnly={readOnly}
/>
) : (
<Input
@ -90,12 +90,13 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
name={name}
disabled={disabled}
type={isVisible ? 'text' : 'password'}
className={`${validClass}`}
className={`${validity || !isValid ? 'invalid' : ''} border-rad-right-0`}
placeholder={placeholder}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
aria-label={ariaLabel}
readOnly={readOnly}
/>
)}
<span

View File

@ -7,6 +7,7 @@ import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getUnit } from 'selectors/transaction';
import { getNetworkUnit } from 'selectors/config';
import { Option } from 'react-select';
interface DispatchProps {
setUnitMeta: TSetUnitMeta;
@ -41,7 +42,10 @@ class UnitDropdownClass extends Component<DispatchProps & StateProps> {
/>
);
}
private handleOnChange = unit => {
private handleOnChange = (unit: Option<string>) => {
if (!unit.value) {
throw Error('No unit value found');
}
this.props.setUnitMeta(unit.value);
};
}

View File

@ -40,7 +40,6 @@ $speed: 500ms;
margin: 0;
}
&:last-child {
margin: 0;
}
@ -80,7 +79,6 @@ $speed: 500ms;
}
&:active {
outline: none;
opacity: 1;
}
@ -106,7 +104,7 @@ $speed: 500ms;
.DecryptContent {
&-enter {
opacity: 0;
transition: opacity $speed * .25 ease $speed * .125;
transition: opacity $speed * 0.25 ease $speed * 0.125;
&-active {
opacity: 1;
@ -119,7 +117,7 @@ $speed: 500ms;
left: 0;
width: 100%;
opacity: 1;
transition: opacity $speed * .25 ease;
transition: opacity $speed * 0.25 ease;
pointer-events: none;
&-active {

View File

@ -126,7 +126,7 @@ type InsecureWallets = { [key in InsecureWalletName]: InsecureWalletInfo };
type MiscWallet = { [key in MiscWalletName]: MiscWalletInfo };
type Wallets = SecureWallets & InsecureWallets & MiscWallet;
const WEB3_TYPE: string | false =
const WEB3_TYPE: keyof typeof WEB3_TYPES | false =
(window as any).web3 && (window as any).web3.currentProvider.constructor.name;
const SECURE_WALLETS = Object.values(SecureWalletName);

View File

@ -73,7 +73,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
this.getAddresses();
}
public componentWillReceiveProps(nextProps) {
public componentWillReceiveProps(nextProps: Props) {
const { publicKey, chainCode, seed, dPath } = this.props;
if (
nextProps.publicKey !== publicKey ||
@ -245,7 +245,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
}
};
private selectAddress(selectedAddress, selectedAddrIndex) {
private selectAddress(selectedAddress: string, selectedAddrIndex: number) {
this.setState({ selectedAddress, selectedAddrIndex });
}

View File

@ -134,7 +134,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
showTip: false
});
ledger.comm_u2f.create_async().then(comm => {
ledger.comm_u2f.create_async().then((comm: any) => {
new ledger.eth(comm)
.getAddress_async(dPath, false, true)
.then(res => {
@ -144,7 +144,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
isLoading: false
});
})
.catch(err => {
.catch((err: any) => {
if (err && err.metaData && err.metaData.code === 5) {
this.showTip();
}

View File

@ -59,7 +59,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
isValid={isValidMnemonic}
isTextareaWhenVisible={true}
onChange={this.onMnemonicChange}
onEnter={isValidMnemonic && this.onDWModalOpen}
onEnter={isValidMnemonic ? this.onDWModalOpen : undefined}
/>
</div>
<div className="form-group">
@ -134,7 +134,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
this.setState({ dPath });
};
private handleUnlock = (address, index) => {
private handleUnlock = (address: string, index: number) => {
const { formattedPhrase, pass, dPath } = this.state;
this.props.onUnlock({

View File

@ -113,7 +113,7 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
(TrezorConnect as any).getXPubKey(
dPath,
res => {
(res: any) => {
if (res.success) {
this.setState({
dPath,

View File

@ -29,7 +29,6 @@
transition: transform 150ms ease, box-shadow 150ms ease;
animation: wallet-button-enter 400ms ease 1;
animation-fill-mode: backwards;
outline: none;
@for $i from 0 to 5 {
&:nth-child(#{$i}) {
@ -60,7 +59,6 @@
}
&.is-disabled {
outline: none;
cursor: not-allowed;
@include show-tooltip-on-hover;

View File

@ -28,6 +28,7 @@ interface Icon {
icon: string;
tooltip: string;
href?: string;
arialabel: string;
}
type Props = OwnProps & StateProps;
@ -50,18 +51,21 @@ export class WalletButton extends React.PureComponent<Props> {
if (isReadOnly) {
icons.push({
icon: 'eye',
tooltip: translateRaw('You cannot send using address only')
tooltip: translateRaw('You cannot send using address only'),
arialabel: 'Read Only'
});
} else {
if (isSecure) {
icons.push({
icon: 'shield',
tooltip: translateRaw('This wallet type is secure')
tooltip: translateRaw('This wallet type is secure'),
arialabel: 'Secure wallet type'
});
} else {
icons.push({
icon: 'exclamation-triangle',
tooltip: translateRaw('This wallet type is insecure')
tooltip: translateRaw('This wallet type is insecure'),
arialabel: 'Insecure wallet type'
});
}
}
@ -69,7 +73,8 @@ export class WalletButton extends React.PureComponent<Props> {
icons.push({
icon: 'question-circle',
tooltip: translateRaw('NAV_Help'),
href: helpLink
href: helpLink,
arialabel: 'More info'
});
}
@ -86,22 +91,30 @@ export class WalletButton extends React.PureComponent<Props> {
>
<div className="WalletButton-inner">
<div className="WalletButton-title">
{icon && <img className="WalletButton-title-icon" src={icon} />}
{icon && <img className="WalletButton-title-icon" src={icon} alt={name + ' logo'} />}
<span>{name}</span>
</div>
{description && <div className="WalletButton-description">{description}</div>}
{example && <div className="WalletButton-example">{example}</div>}
{description && (
<div className="WalletButton-description" aria-label="description">
{description}
</div>
)}
{example && (
<div className="WalletButton-example" aria-label="example" aria-hidden={true}>
{example}
</div>
)}
<div className="WalletButton-icons">
{icons.map(i => (
<span className="WalletButton-icons-icon" key={i.icon} onClick={this.stopPropogation}>
{i.href ? (
<NewTabLink href={i.href} onClick={this.stopPropogation}>
<NewTabLink href={i.href} onClick={this.stopPropogation} aria-label={i.arialabel}>
<i className={`fa fa-${i.icon}`} />
</NewTabLink>
) : (
<i className={`fa fa-${i.icon}`} />
<i className={`fa fa-${i.icon}`} aria-label={i.arialabel} />
)}
{!isDisabled && <Tooltip size="sm">{i.tooltip}</Tooltip>}
</span>

View File

@ -92,6 +92,7 @@ export default class ColorDropdown<T> extends PureComponent<Props<T>, {}> {
className="ColorDropdown-item-remove"
onClick={this.onRemove.bind(null, option.onRemove)}
src={removeIcon}
alt="remove"
/>
)}
</a>

View File

@ -1,5 +1,5 @@
import React from 'react';
import Select, { ReactSelectProps } from 'react-select';
import Select, { ReactSelectProps, Option } from 'react-select';
interface Props extends ReactSelectProps {
className?: string;
@ -13,11 +13,11 @@ export default class Dropdown extends React.Component<Props> {
hasBlurred: false
};
public handleChange = selectedOption => {
public handleChange = (selectedOption: Option) => {
this.setState({ selectedOption });
};
public formatOptions = options => {
public formatOptions = (options: Option[]) => {
if (typeof options[0] === 'object') {
return options;
}
@ -38,7 +38,7 @@ export default class Dropdown extends React.Component<Props> {
// use ref to prevent <label /> from stealing focus when used inline with an input
ref={el => {
if (!!el && !!(el as any).control) {
(el as any).control.addEventListener('click', e => {
(el as any).control.addEventListener('click', (e: React.FormEvent<any>) => {
e.preventDefault();
});
}
@ -46,7 +46,7 @@ export default class Dropdown extends React.Component<Props> {
className={`${this.props.className} ${this.state.hasBlurred ? 'has-blurred' : ''}`}
value={value}
onChange={obj => {
this.handleChange(obj);
this.handleChange(obj as any);
onChange();
}}
{...this.props}
@ -56,7 +56,7 @@ export default class Dropdown extends React.Component<Props> {
this.props.onBlur(e);
}
}}
options={options}
options={options as any}
/>
);
}

View File

@ -12,7 +12,7 @@ interface Props {
const Help = ({ size = 'x1', link }: Props) => {
return (
<a href={link} className={`Help Help-${size}`} target="_blank" rel="noopener noreferrer">
<img src={icon} />
<img src={icon} alt="help" />
</a>
);
};

View File

@ -24,6 +24,7 @@ export default function Identicon(props: Props) {
<React.Fragment>
<img
src={identiconDataUrl}
alt="Unique Address Image"
style={{
height: '100%',
width: '100%',

View File

@ -12,6 +12,22 @@
> .TogglablePassword {
width: 100%;
}
&-inline {
display: flex;
flex-direction: row;
font-size: 1rem;
flex-wrap: wrap;
> .input-group-header {
width: 100%;
}
> .input-group-input {
flex-grow: 1;
width: auto;
}
> .Select {
margin-left: 8px;
}
}
&-header {
display: flex;
font-size: 1rem;
@ -29,6 +45,9 @@
color: rgba(0, 0, 0, 0.54);
}
}
&-dropdown {
margin-bottom: 1rem;
}
&-input {
width: 100%;
border: 1px solid #e5ecf3;
@ -40,6 +59,17 @@
box-shadow: inset 0 1px 0 0 rgba(63, 63, 68, 0.05);
transition: border-color 120ms, box-shadow 120ms;
margin-bottom: 1rem;
&.border-rad-right-0 {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&.border-rad-left-0 {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&-small {
padding: 0.5rem 0.75rem;
}
&::placeholder {
color: rgba(0, 0, 0, 0.3);
}
@ -60,23 +90,6 @@
}
}
.input-group-inline-dropdown {
display: flex;
flex-direction: row;
font-size: 1rem;
flex-wrap: wrap;
> .input-group-header {
width: 100%;
}
> .input-group-input {
flex-grow: 1;
width: auto;
}
> .Select {
margin-left: 8px;
}
}
.Swap-dropdown {
.Select-input {
left: 24px;

View File

@ -1,149 +0,0 @@
import closeIcon from 'assets/images/close.svg';
import React, { PureComponent } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './Modal.scss';
export interface IButton {
text: string | React.ReactElement<string>;
type?: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'link';
disabled?: boolean;
onClick?(): void;
}
interface Props {
isOpen?: boolean;
title?: string | React.ReactElement<any>;
disableButtons?: boolean;
children: any;
buttons?: IButton[];
maxWidth?: number;
handleClose?(): void;
}
interface ModalStyle {
width?: string;
maxWidth?: string;
}
const Fade = ({ children, ...props }) => (
<CSSTransition {...props} timeout={300} classNames="animate-modal">
{children}
</CSSTransition>
);
export default class Modal extends PureComponent<Props, {}> {
private modalContent: HTMLElement | null = null;
public componentDidMount() {
this.updateBodyClass();
document.addEventListener('keydown', this.escapeListner);
}
public componentDidUpdate() {
this.updateBodyClass();
}
public updateBodyClass() {
document.body.classList.toggle('no-scroll', !!this.props.isOpen);
}
public componentWillUnmount() {
document.removeEventListener('keydown', this.escapeListner);
document.body.classList.remove('no-scroll');
}
public render() {
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
const hasButtons = buttons && buttons.length;
const modalStyle: ModalStyle = {};
if (maxWidth) {
modalStyle.width = '100%';
modalStyle.maxWidth = `${maxWidth}px`;
}
return (
<TransitionGroup>
{isOpen && (
<Fade>
<div>
<div className="Modalshade" />
<div className="Modal" style={modalStyle}>
{title && (
<div className="Modal-header flex-wrapper">
<h2 className="Modal-header-title">{title}</h2>
<div className="flex-spacer" />
<button className="Modal-header-close" onClick={handleClose}>
<img className="Modal-header-close-icon" src={closeIcon} />
</button>
</div>
)}
<div className="Modal-content" ref={el => (this.modalContent = el)}>
{isOpen && children}
<div className="Modal-fade" />
</div>
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
</div>
</div>
</Fade>
)}
</TransitionGroup>
);
}
public scrollContentToTop = () => {
if (this.modalContent) {
this.modalContent.scrollTop = 0;
}
};
private escapeListner = (ev: KeyboardEvent) => {
if (!this.props.isOpen) {
return;
}
// Don't trigger if they hit escape while on an input
if (ev.target) {
if (
(ev.target as HTMLElement).tagName === 'INPUT' ||
(ev.target as HTMLElement).tagName === 'SELECT' ||
(ev.target as HTMLElement).tagName === 'TEXTAREA' ||
(ev.target as HTMLElement).isContentEditable
) {
return;
}
}
if (ev.key === 'Escape' || ev.keyCode === 27) {
if (!this.props.handleClose) {
return;
}
this.props.handleClose();
}
};
private renderButtons = () => {
const { disableButtons, buttons } = this.props;
if (!buttons || !buttons.length) {
return;
}
return buttons.map((btn, idx) => {
let btnClass = 'Modal-footer-btn btn';
if (btn.type) {
btnClass += ` btn-${btn.type}`;
}
return (
<button
className={btnClass}
onClick={btn.onClick}
key={idx}
disabled={disableButtons || btn.disabled}
>
{btn.text}
</button>
);
});
};
}

View File

@ -0,0 +1,131 @@
import React, { CSSProperties } from 'react';
import closeIcon from 'assets/images/close.svg';
import { IButton } from 'components/ui/Modal';
interface Props {
title?: string;
children: any;
modalStyle?: CSSProperties;
hasButtons?: number;
buttons?: IButton[];
disableButtons?: any;
handleClose(): void;
}
export default class ModalBody extends React.Component<Props> {
private modal: HTMLElement;
private modalContent: HTMLElement;
private focusedElementBeforeModal: HTMLElement;
private firstTabStop: HTMLElement;
private lastTabStop: HTMLElement;
public componentDidMount() {
this.focusedElementBeforeModal = document.activeElement as HTMLElement;
// Find all focusable children
const focusableElementsString =
'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
const focusableElements = Array.prototype.slice.call(
this.modal.querySelectorAll(focusableElementsString)
);
// Convert NodeList to Array
this.firstTabStop = focusableElements[0];
this.lastTabStop = focusableElements[focusableElements.length - 1];
// Focus first child
this.firstTabStop.focus();
this.modal.addEventListener('keydown', this.keyDownListener);
}
public componentWillUnmount() {
document.removeEventListener('keydown', this.keyDownListener);
}
public scrollContentToTop = () => {
this.modalContent.scrollTop = 0;
};
public render() {
const { title, children, modalStyle, hasButtons, handleClose } = this.props;
return (
<div
className="Modal"
style={modalStyle}
role="dialog"
aria-labelledby="Modal-header-title"
ref={div => {
this.modal = div as HTMLElement;
}}
>
{title && (
<div className="Modal-header flex-wrapper">
<h2 className="Modal-header-title">{title}</h2>
<div className="flex-spacer" />
<button className="Modal-header-close" aria-label="Close" onClick={handleClose}>
<img className="Modal-header-close-icon" src={closeIcon} alt="Close" />
</button>
</div>
)}
<div className="Modal-content" ref={div => (this.modalContent = div as HTMLElement)}>
{children}
<div className="Modal-fade" />
</div>
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
</div>
);
}
private renderButtons = () => {
const { disableButtons, buttons } = this.props;
if (!buttons || !buttons.length) {
return;
}
return buttons.map((btn, idx: number) => {
let btnClass = 'Modal-footer-btn btn';
if (btn.type) {
btnClass += ` btn-${btn.type}`;
}
return (
<button
className={btnClass}
onClick={btn.onClick}
key={idx}
disabled={disableButtons || btn.disabled}
>
{btn.text}
</button>
);
});
};
private keyDownListener = (e: KeyboardEvent) => {
// Check for TAB key press
if (e.keyCode === 9) {
// SHIFT + TAB
if (e.shiftKey) {
if (document.activeElement === this.firstTabStop) {
e.preventDefault();
this.lastTabStop.focus();
}
// TAB
} else {
if (document.activeElement === this.lastTabStop) {
e.preventDefault();
this.firstTabStop.focus();
}
}
}
// Check for ESC key press
if (e.keyCode === 27) {
this.focusedElementBeforeModal.focus();
this.props.handleClose();
}
};
}

View File

@ -13,17 +13,6 @@ $m-footer-padding: 0.5rem 2rem 1rem 2rem;
$m-close-size: 26px;
$m-anim-speed: 400ms;
.Modalshade {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(#000, 0.54);
z-index: $zindex-modal-background;
display: block;
}
.Modal {
position: fixed;
top: $m-window-padding-h;
@ -45,6 +34,17 @@ $m-anim-speed: 400ms;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14),
0px 6px 30px 5px rgba(0, 0, 0, 0.12);
&-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(#000, 0.54);
z-index: $zindex-modal-background;
display: block;
}
&-fade {
background: linear-gradient(to bottom, #fff0, #fff);
position: fixed;
@ -112,7 +112,7 @@ $m-anim-speed: 400ms;
}
// Mobile styles
@media(max-width: $screen-sm) {
@media (max-width: $screen-sm) {
top: $m-window-padding-h-mobile;
width: calc(100% - #{$m-window-padding-w-mobile}) !important;
max-width: calc(100% - #{$m-window-padding-w-mobile * 2});

View File

@ -0,0 +1,70 @@
import React, { PureComponent } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import ModalBody from './ModalBody';
import './index.scss';
export interface IButton {
text: string | React.ReactElement<string>;
type?: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'link';
disabled?: boolean;
onClick?(): void;
}
interface Props {
isOpen?: boolean;
title?: string;
disableButtons?: boolean;
children: any;
buttons?: IButton[];
maxWidth?: number;
handleClose(): void;
}
interface ModalStyle {
width?: string;
maxWidth?: string;
}
const Fade = ({ children, ...props }: any) => (
<CSSTransition {...props} timeout={300} classNames="animate-modal">
{children}
</CSSTransition>
);
export default class Modal extends PureComponent<Props, {}> {
public modalBody: ModalBody;
public componentDidUpdate(prevProps: Props) {
if (prevProps.isOpen !== this.props.isOpen) {
document.body.classList.toggle('no-scroll', !!this.props.isOpen);
}
}
public componentWillUnmount() {
document.body.classList.remove('no-scroll');
}
public render() {
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
const hasButtons = buttons && buttons.length;
const modalStyle: ModalStyle = {};
if (maxWidth) {
modalStyle.width = '100%';
modalStyle.maxWidth = `${maxWidth}px`;
}
const modalBodyProps = { title, children, modalStyle, hasButtons, buttons, handleClose };
return (
<TransitionGroup>
{isOpen && (
<Fade>
<div>
<div className="Modal-overlay" onClick={handleClose} />
<ModalBody {...modalBodyProps} ref={div => (this.modalBody = div as ModalBody)} />
</div>
</Fade>
)}
</TransitionGroup>
);
}
}

View File

@ -26,7 +26,7 @@ const OfflineSymbol = ({ offline, size }: OfflineSymbolProps) => {
break;
}
return <img src={offline ? wifiOff : wifiOn} width={width} height={height} />;
return <img src={offline ? wifiOff : wifiOn} alt="wifi status" width={width} height={height} />;
};
export default OfflineSymbol;

View File

@ -64,8 +64,8 @@ export default class DropdownComponent<T> extends PureComponent<Props<T>, State>
overflowY: 'auto'
};
const searchRegex = new RegExp(search, 'gi');
const onSearchChange = e => {
this.setState({ search: e.target.value });
const onSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
this.setState({ search: e.currentTarget.value });
};
return (

View File

@ -35,6 +35,7 @@ export default class QRCode extends React.PureComponent<Props, State> {
return (
<img
src={qr}
alt="QR Code"
style={{
width: '100%',
height: '100%'

View File

@ -11,7 +11,7 @@ interface SpinnerProps {
const Spinner = ({ size = 'x1', light = false }: SpinnerProps) => {
const color = light ? 'Spinner-light' : 'Spinner-dark';
return (
<svg className={`Spinner Spinner-${size} ${color}`} viewBox="0 0 50 50">
<svg className={`Spinner Spinner-${size} ${color}`} viewBox="0 0 50 50" aria-busy="true">
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="5" />
</svg>
);

View File

@ -8,9 +8,6 @@
padding: 0.4rem 1rem;
border-radius: 2px;
height: 2.5rem;
&:focus {
outline: none;
}
&:active,
&:hover {
opacity: 0.8;

View File

@ -18,22 +18,22 @@ interface Props<T> {
const ValueComp: React.SFC = (props: any) => {
return (
<div className={`${props.className} swap-option-wrapper`}>
<img src={props.value.img} className="swap-option-img" />
<img src={props.value.img} className="swap-option-img" alt={props.value.label + ' logo'} />
<span className="swap-option-label">{props.value.label}</span>
</div>
);
};
const OptionComp: React.SFC = (props: any) => {
const handleMouseDown = event => {
const handleMouseDown = (event: React.MouseEvent<any>) => {
event.preventDefault();
event.stopPropagation();
props.onSelect(props.option, event);
};
const handleMouseEnter = event => {
const handleMouseEnter = (event: React.MouseEvent<any>) => {
props.onFocus(props.option, event);
};
const handleMouseMove = event => {
const handleMouseMove = (event: React.MouseEvent<any>) => {
if (props.isFocused) {
return;
}
@ -46,7 +46,7 @@ const OptionComp: React.SFC = (props: any) => {
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
>
<img src={props.option.img} className="swap-option-img" />
<img src={props.option.img} className="swap-option-img" alt={props.option.label + ' logo'} />
<span className="swap-option-label">{props.option.label}</span>
</div>
);

View File

@ -6,7 +6,11 @@ import { HELP_ARTICLE } from 'config';
import OnboardSlide from './OnboardSlide';
import onboardIconTen from 'assets/images/onboarding/slide-10.svg';
const FinalSlide = ({ closeModal }) => {
interface Props {
closeModal(): void;
}
const FinalSlide: React.SFC<Props> = ({ closeModal }) => {
const header = translate('ONBOARD_final_title');
const subheader = translate('ONBOARD_final_subtitle');

View File

@ -49,7 +49,7 @@ interface Props {
class OnboardModal extends React.Component<Props, State> {
private modal: Modal | null = null;
constructor(props) {
constructor(props: Props) {
super(props);
this.state = {
isOpen: false
@ -120,7 +120,11 @@ class OnboardModal extends React.Component<Props, State> {
return (
<div className="OnboardModal">
<Modal isOpen={isOpen} buttons={buttons} ref={el => (this.modal = el)}>
<Modal
isOpen={isOpen}
buttons={buttons}
handleClose={() => (slideNumber === NUMBER_OF_SLIDES ? this.closeModal : null)}
>
<div className="OnboardModal-stepper">
<Stepper
steps={steps}
@ -171,7 +175,7 @@ class OnboardModal extends React.Component<Props, State> {
localStorage.setItem(ONBOARD_LOCAL_STORAGE_KEY, String(prevSlideNum));
this.props.decrementSlide();
if (this.modal) {
this.modal.scrollContentToTop();
this.modal.modalBody.scrollContentToTop();
}
};
@ -180,7 +184,7 @@ class OnboardModal extends React.Component<Props, State> {
localStorage.setItem(ONBOARD_LOCAL_STORAGE_KEY, String(nextSlideNum));
this.props.incrementSlide();
if (this.modal) {
this.modal.scrollContentToTop();
this.modal.modalBody.scrollContentToTop();
}
};
}

View File

@ -14,7 +14,7 @@ interface Props {
export class Notifications extends React.Component<Props, {}> {
public render() {
return (
<TransitionGroup className="Notifications">
<TransitionGroup className="Notifications" aria-live="polite">
{this.props.notifications.map(n => {
return (
<CSSTransition classNames="NotificationAnimation" timeout={500} key={n.id}>

View File

@ -36,7 +36,7 @@ interface State {
inputs: {
[key: string]: { rawData: string; parsedData: string[] | string };
};
outputs;
outputs: any;
selectedFunction: null | ContractOption;
}
@ -124,7 +124,7 @@ class InteractExplorerClass extends Component<Props, State> {
</div>
);
})}
{selectedFunction.contract.outputs.map((output, index) => {
{selectedFunction.contract.outputs.map((output: any, index: number) => {
const { type, name } = output;
const parsedName = name === '' ? index : name;

View File

@ -20,7 +20,7 @@ interface StateProps {
}
interface OwnProps {
accessContract(contractAbi: string, address: string): (ev) => void;
accessContract(contractAbi: string, address: string): (ev: any) => void;
resetState(): void;
}
@ -45,7 +45,7 @@ const abiJsonPlaceholder = [
class InteractForm extends Component<Props, State> {
private abiJsonPlaceholder = JSON.stringify(abiJsonPlaceholder, null, 0);
constructor(props) {
constructor(props: Props) {
super(props);
this.state = {
address: '',
@ -140,7 +140,9 @@ class InteractForm extends Component<Props, State> {
);
}
private handleInput = name => (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>) => {
private handleInput = (name: any) => (
ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
this.props.resetState();
this.setState({ [name]: ev.currentTarget.value });
};

View File

@ -33,10 +33,12 @@ class NameInput extends Component<Props, State> {
return (
<form className="ENSInput" onSubmit={this.onSubmit}>
<div className="input-group-wrapper">
<label className="input-group input-group-inline-dropdown ENSInput-name">
<label className="input-group input-group-inline ENSInput-name">
<Input
value={domainToCheck}
className={!domainToCheck ? '' : isValidDomain ? 'is-valid' : 'is-invalid'}
className={`${
!domainToCheck ? '' : isValidDomain ? '' : 'invalid'
} border-rad-right-0`}
type="text"
placeholder="mycrypto"
onChange={this.onChange}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { IOwnedDomainRequest } from 'libs/ens';
import { NewTabLink, Address } from 'components/ui';
const lookupLink = name => `https://etherscan.io/enslookup?q=${name}`;
const lookupLink = (name: string) => `https://etherscan.io/enslookup?q=${name}`;
type ChildrenProps = any;

View File

@ -22,7 +22,7 @@ class CountDown extends Component<Props, State> {
private startCountDown = () => {
const time = moment(this.props.initialTime);
let intervalId;
let intervalId: number;
const setTimeDisplay = () => {
const diff = moment.duration(time.diff(moment()));
@ -44,7 +44,7 @@ class CountDown extends Component<Props, State> {
this.setState({ timeDisplay });
};
intervalId = setInterval(setTimeDisplay, 1000);
intervalId = window.setInterval(setTimeDisplay, 1000);
setTimeDisplay();
};
}

View File

@ -53,7 +53,11 @@ const CryptoWarning: React.SFC<{}> = () => (
className="CryptoWarning-browsers-browser"
>
<div>
<img className="CryptoWarning-browsers-browser-icon" src={browser.icon} />
<img
className="CryptoWarning-browsers-browser-icon"
src={browser.icon}
alt={browser.name + ' logo'}
/>
<div className="CryptoWarning-browsers-browser-name">{browser.name}</div>
</div>
</NewTabLink>

View File

@ -1,4 +1,4 @@
@import "common/sass/variables";
@import 'common/sass/variables';
.GenPaper {
&-title {
@ -6,8 +6,17 @@
}
&-private {
max-width: 700px;
max-width: 680px;
margin: 0 auto $space * 3;
margin-top: 12px;
> .input-group-header {
margin-bottom: 1rem;
// This selector is an exception, it targets the span returned using `translate`.
> span {
font-size: 2rem;
margin: auto;
}
}
}
&-paper,

View File

@ -1,7 +1,7 @@
import PrintableWallet from 'components/PrintableWallet';
import { IV3Wallet } from 'ethereumjs-wallet';
import React from 'react';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import { stripHexPrefix } from 'libs/values';
import './PaperWallet.scss';
import Template from '../Template';
@ -17,18 +17,20 @@ const PaperWallet: React.SFC<Props> = props => (
<Template>
<div className="GenPaper">
{/* Private Key */}
<h1 className="GenPaper-title">{translate('GEN_Label_5')}</h1>
<Input
className="GenPaper-private"
value={stripHexPrefix(props.privateKey)}
aria-label={translate('x_PrivKey', true)}
aria-describedby="x_PrivKeyDesc"
type="text"
readOnly={true}
/>
<label className="input-group GenPaper-private">
{/* translateRaw isn't used here because it wont properly render the ` characters as a string of code in markdown*/}
<h1 className="input-group-header">{translate('GEN_Label_5')}</h1>
<Input
value={stripHexPrefix(props.privateKey)}
aria-label={translateRaw('x_PrivKey')}
aria-describedby="x_PrivKeyDesc"
type="text"
readOnly={true}
/>
</label>
{/* Download Paper Wallet */}
<h1 className="GenPaper-title">{translate('x_Print')}</h1>
<h2 className="GenPaper-title">{translate('x_Print')}</h2>
<div className="GenPaper-paper">
<PrintableWallet address={props.keystore.address} privateKey={props.privateKey} />
</div>

View File

@ -28,10 +28,10 @@ export default class MnemonicWord extends React.Component<Props, State> {
return (
<div className="input-group-wrapper MnemonicWord">
<label className="input-group input-group-inline-dropdown ENSInput-name">
<label className="input-group input-group-inline ENSInput-name">
<span className="input-group-addon input-group-addon--transparent">{index + 1}.</span>
<Input
className={classnames('MnemonicWord-word-input', word === value && 'valid')}
className={`MnemonicWord-word-input ${!isReadOnly && 'border-rad-right-0'}`}
value={readOnly ? word : value}
onChange={this.handleChange}
readOnly={readOnly}

View File

@ -14,7 +14,6 @@
top: 36px;
left: 30px;
opacity: 0.3;
outline: none;
color: $text-color;
@media (max-width: $screen-sm) {

View File

@ -46,7 +46,7 @@ const WalletTypes: React.SFC<{}> = () => {
<div className="WalletTypes-types row">
<div className="col-md-1" />
{Object.keys(typeInfo).map(type => (
{Object.keys(typeInfo).map((type: keyof typeof typeInfo) => (
<div key={type} className="WalletType col-md-5">
<h2 className="WalletType-title">{translate(typeInfo[type].name)}</h2>
<ul className="WalletType-features">

View File

@ -44,7 +44,8 @@ interface ActionProps {
type Props = OwnProps & StateProps & ActionProps;
const isValidAmount = decimal => amount => validNumber(+amount) && validDecimal(amount, decimal);
const isValidAmount = (decimal: number) => (amount: string) =>
validNumber(+amount) && validDecimal(amount, decimal);
class RequestPayment extends React.Component<Props, {}> {
public state = {
@ -145,7 +146,7 @@ class RequestPayment extends React.Component<Props, {}> {
private generateEIP681String(
currentTo: string,
tokenContractAddress: string,
currentValue,
currentValue: { raw: string; value: BN | null },
gasLimit: { raw: string; value: BN | null },
unit: string,
decimal: number,
@ -162,7 +163,11 @@ class RequestPayment extends React.Component<Props, {}> {
return '';
}
if (this.props.isNetworkUnit) {
const currentValueIsEther = (
_: AppState['transaction']['fields']['value'] | AppState['transaction']['meta']['tokenTo']
): _ is AppState['transaction']['fields']['value'] => this.props.isNetworkUnit;
if (currentValueIsEther(currentValue)) {
return buildEIP681EtherRequest(currentTo, chainId, currentValue);
} else {
return buildEIP681TokenRequest(

View File

@ -30,8 +30,8 @@ export interface ActionProps {
interface State {
disabled: boolean;
origin: SwapInput;
destination: SwapInput;
origin: SwapOpt;
destination: SwapOpt;
originKindOptions: any[];
destinationKindOptions: any[];
originErr: string;
@ -49,7 +49,7 @@ interface SwapOpt extends SwapInput {
}
export default class CurrencySwap extends PureComponent<Props, State> {
public state = {
public state: State = {
disabled: true,
origin: {
label: 'BTC',
@ -57,14 +57,14 @@ export default class CurrencySwap extends PureComponent<Props, State> {
status: 'available',
image: 'https://shapeshift.io/images/coins/bitcoin.png',
amount: NaN
} as SwapOpt,
},
destination: {
label: 'ETH',
value: 'Ether',
status: 'available',
image: 'https://shapeshift.io/images/coins/ether.png',
amount: NaN
} as SwapOpt,
},
originKindOptions: [],
destinationKindOptions: [],
originErr: '',
@ -151,7 +151,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
return merge(shapeshiftRates, bityRates);
};
public getMinMax = (originKind: WhitelistedCoins, destinationKind) => {
public getMinMax = (originKind: WhitelistedCoins, destinationKind: string) => {
let min;
let max;
@ -176,7 +176,11 @@ export default class CurrencySwap extends PureComponent<Props, State> {
return { min, max };
};
public isMinMaxValid = (originAmount: number, originKind: WhitelistedCoins, destinationKind) => {
public isMinMaxValid = (
originAmount: number,
originKind: WhitelistedCoins,
destinationKind: string
) => {
const rate = this.getMinMax(originKind, destinationKind);
const higherThanMin = originAmount >= rate.min;
const lowerThanMax = originAmount <= rate.max;
@ -201,7 +205,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
this.debouncedCreateErrString(origin, destination, showError);
}
public setErrorMessages = (originErr, destinationErr) => {
public setErrorMessages = (originErr: string, destinationErr: string) => {
this.setState({
originErr,
destinationErr
@ -269,15 +273,17 @@ export default class CurrencySwap extends PureComponent<Props, State> {
: this.updateDestinationAmount(origin, destination, amount);
};
public onChangeOriginKind = newOption => {
public onChangeOriginKind = (newOption: any) => {
const { origin, destination, destinationKindOptions } = this.state;
const { options, initSwap } = this.props;
const newOrigin = { ...origin, label: newOption.label, value: newOption.value, amount: '' };
const newOrigin = { ...origin, label: newOption.label, value: newOption.value, amount: 0 };
const newDest = {
label: newOption.label === destination.label ? origin.label : destination.label,
value: newOption.value === destination.value ? origin.value : destination.value,
amount: ''
amount: 0,
status: '',
image: ''
};
this.setState({
@ -292,16 +298,16 @@ export default class CurrencySwap extends PureComponent<Props, State> {
initSwap({ origin: newOrigin, destination: newDest });
};
public onChangeDestinationKind = newOption => {
public onChangeDestinationKind = (newOption: any) => {
const { initSwap } = this.props;
const { origin, destination } = this.state;
const newOrigin = {
...origin,
amount: ''
amount: 0
};
const newDest = { ...destination, label: newOption.label, value: newOption.value, amount: '' };
const newDest = { ...destination, label: newOption.label, value: newOption.value, amount: 0 };
this.setState({
origin: newOrigin,
destination: newDest
@ -335,11 +341,11 @@ export default class CurrencySwap extends PureComponent<Props, State> {
<div className="flex-spacer" />
<div className="input-group-wrapper">
<div className="input-group-header">Deposit</div>
<label className="input-group input-group-inline-dropdown">
<label className="input-group input-group-inline">
<Input
id="origin-swap-input"
className={`input-group-input ${
String(origin.amount) !== '' &&
!origin.amount &&
this.isMinMaxValid(origin.amount, origin.label, destination.label)
? ''
: 'invalid'
@ -359,12 +365,12 @@ export default class CurrencySwap extends PureComponent<Props, State> {
</div>
<div className="input-group-wrapper">
<label className="input-group input-group-inline-dropdown">
<label className="input-group input-group-inline">
<div className="input-group-header">Recieve</div>
<Input
id="destination-swap-input"
className={`${
String(destination.amount) !== '' &&
!destination.amount &&
this.isMinMaxValid(origin.amount, origin.label, destination.label)
? ''
: 'invalid'

View File

@ -2,6 +2,8 @@
@import "common/sass/mixins";
.SwapRates {
margin-bottom: $space;
&-title {
margin-top: 0;
text-align: center;

View File

@ -24,6 +24,7 @@ import { getOffline } from 'selectors/config';
import Rates from './Rates';
import { AppState } from 'reducers';
import './CurrentRates.scss';
import { Optional } from 'utils/types';
interface StateProps {
isOffline: boolean;
@ -40,7 +41,7 @@ interface ActionProps {
type Props = StateProps & ActionProps;
class CurrentRates extends PureComponent<Props> {
private shapeShiftRateCache = null;
private shapeShiftRateCache: any = null;
public componentDidMount() {
if (!this.props.isOffline) {
@ -79,7 +80,7 @@ class CurrentRates extends PureComponent<Props> {
public buildSSPairs = (shapeshiftRates: NormalizedShapeshiftRates, n: number = 4) => {
const pairCollection = times(n, () => this.getRandomSSPairData(shapeshiftRates));
const byId = pairCollection.reduce((acc, cur) => {
const byId = pairCollection.reduce<{ [id: string]: NormalizedShapeshiftRate }>((acc, cur) => {
acc[cur.id] = cur;
return acc;
}, {});
@ -90,7 +91,7 @@ class CurrentRates extends PureComponent<Props> {
};
};
public isValidRates = rates => {
public isValidRates = (rates: Optional<NormalizedShapeshiftRates>) => {
return rates && rates.allIds && rates.allIds.length > 0;
};
@ -118,7 +119,7 @@ class CurrentRates extends PureComponent<Props> {
return fixedRates;
};
public swapEl = (providerURL, providerLogo, children) => {
public swapEl = (providerURL: string, providerLogo: string, children: any) => {
return (
<article className="SwapRates">
<h3 className="SwapRates-title">{translate('SWAP_rates')}</h3>
@ -131,7 +132,7 @@ class CurrentRates extends PureComponent<Props> {
target="_blank"
rel="noopener noreferrer"
>
<img src={providerLogo} width={120} height={49} />
<img src={providerLogo} width={120} height={49} alt="Shapeshift Logo" />
</a>
</section>
</article>

View File

@ -5,7 +5,8 @@ import {
TStartPollShapeshiftOrderStatus,
TStopOrderTimerSwap,
TStopPollBityOrderStatus,
TStopPollShapeshiftOrderStatus
TStopPollShapeshiftOrderStatus,
TStartOrderTimerSwap
} from 'actions/swap';
import { SwapInput } from 'reducers/swap/types';
import React, { PureComponent } from 'react';
@ -28,6 +29,7 @@ interface ReduxStateProps {
}
interface ReduxActionProps {
startOrderTimerSwap: TStartOrderTimerSwap;
restartSwap: TRestartSwap;
startPollBityOrderStatus: TStartPollBityOrderStatus;
stopPollBityOrderStatus: TStopPollBityOrderStatus;
@ -45,6 +47,7 @@ export default class PartThree extends PureComponent<ReduxActionProps & ReduxSta
} else {
this.props.startPollBityOrderStatus();
}
this.props.startOrderTimerSwap();
}
public componentWillUnmount() {

View File

@ -47,7 +47,7 @@ interface State {
}
export default class Rates extends Component<Props, State> {
public state = {
public state: State = {
pairs: {}
};
@ -72,7 +72,7 @@ export default class Rates extends Component<Props, State> {
public getPairs = () => {
const { rates } = this.props;
const { allIds } = rates;
return allIds.reduce((acc, cur) => {
return allIds.reduce<{ [id: string]: 1 }>((acc, cur) => {
acc[cur] = 1;
return acc;
}, {});

View File

@ -1,31 +0,0 @@
@import 'common/sass/variables';
.ShapeshiftBanner {
margin: $space 0;
text-align: center;
&-banner {
display: inline-block;
text-align: left;
padding: $space-sm $space;
background-color: #3a526d;
border-radius: 3px;
box-shadow: 0 3px 8px 0 rgba(0,0,0,0.1), inset 0 0 3px 0 rgba(0,0,0,0.1);
p {
display: inline-block;
color: white;
vertical-align: middle;
margin-bottom: 0px;
b {
margin-right: 5px;
}
}
img {
display: inline-block;
height: 32px;
box-sizing: content-box;
margin-left: 8px;
}
}
}

View File

@ -1,17 +0,0 @@
import React from 'react';
import './ShapeshiftBanner.scss';
import shapeshiftSvg from 'assets/images/logo-shapeshift.svg';
const ShapeshiftBanner: React.SFC<{}> = () => (
<div className="ShapeshiftBanner">
<div className="ShapeshiftBanner-banner">
<p>
<b className="ShapeshiftBanner-banner-new">New Feature:</b>
Exchange coins & tokens with
</p>
<img src={shapeshiftSvg} />
</div>
</div>
);
export default ShapeshiftBanner;

View File

@ -1,7 +1,7 @@
import { RestartSwapAction } from 'actions/swap';
import bityLogo from 'assets/images/logo-bity.svg';
import shapeshiftLogo from 'assets/images/shapeshift-dark.svg';
import { bityReferralURL } from 'config';
import { shapeshiftReferralURL, bitboxReferralURL } from 'config';
import React, { PureComponent } from 'react';
import translate from 'translations';
import './SwapInfoHeader.scss';
@ -29,11 +29,11 @@ export default class SwapInfoHeaderTitle extends PureComponent<SwapInfoHeaderTit
<div className="col-xs-3">
<a
className="SwapInfo-top-logo"
href={bityReferralURL}
href={provider === 'shapeshift' ? shapeshiftReferralURL : bitboxReferralURL}
target="_blank"
rel="noopener noreferrer"
>
<img className="SwapInfo-top-logo-img" src={logoToRender} />
<img className="SwapInfo-top-logo-img" src={logoToRender} alt={provider + ' logo'} />
</a>
</div>
</section>

View File

@ -47,7 +47,6 @@ import PartThree from './components/PartThree';
import SupportFooter from './components/SupportFooter';
import ReceivingAddress from './components/ReceivingAddress';
import SwapInfoHeader from './components/SwapInfoHeader';
import ShapeshiftBanner from './components/ShapeshiftBanner';
import TabSection from 'containers/TabSection';
import { merge } from 'lodash';
import { RouteNotFound } from 'components/RouteNotFound';
@ -219,7 +218,6 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps & RouteComponent
render={() => (
<React.Fragment>
{step === 1 && <CurrentRates />}
{step === 1 && <ShapeshiftBanner />}
{(step === 2 || step === 3) && <SwapInfoHeader {...SwapInfoHeaderProps} />}
<main className="Tab-content-pane">
{step === 1 && <CurrencySwap {...CurrencySwapProps} />}

View File

@ -1,7 +1,14 @@
import abi from 'ethereumjs-abi';
import { toChecksumAddress, addHexPrefix } from 'ethereumjs-util';
import BN from 'bn.js';
import { FuncParams, FunctionOutputMappings, Output, Input } from './types';
import {
FuncParams,
FunctionOutputMappings,
Output,
Input,
ITypeMapping,
ISuppliedArgs
} from './types';
export default class AbiFunction {
public constant: boolean;
@ -53,7 +60,6 @@ export default class AbiFunction {
// Convert argdata to a hex buffer for ethereumjs-abi
const argBuffer = new Buffer(argString, 'hex');
// Decode!
const argArr = abi.rawDecode(this.outputTypes, argBuffer);
@ -80,13 +86,13 @@ export default class AbiFunction {
}
private parsePostDecodedValue = (type: string, value: any) => {
const valueMapping = {
address: val => toChecksumAddress(val.toString(16))
const valueMapping: ITypeMapping = {
address: (val: any) => toChecksumAddress(val.toString(16))
};
return valueMapping[type]
? valueMapping[type](value)
: BN.isBN(value) ? value.toString() : value;
const mapppedType = valueMapping[type];
return mapppedType ? mapppedType(value) : BN.isBN(value) ? value.toString() : value;
};
private parsePreEncodedValue = (_: string, value: any) =>
@ -95,7 +101,7 @@ export default class AbiFunction {
private makeFuncParams = () =>
this.inputs.reduce((accumulator, currInput) => {
const { name, type } = currInput;
const inputHandler = inputToParse =>
const inputHandler = (inputToParse: any) =>
//TODO: introduce typechecking and typecasting mapping for inputs
({ name, type, value: this.parsePreEncodedValue(type, inputToParse) });
@ -110,7 +116,7 @@ export default class AbiFunction {
return addHexPrefix(`${this.methodSelector}${encodedArgs}`);
};
private processSuppliedArgs = (suppliedArgs: object) =>
private processSuppliedArgs = (suppliedArgs: ISuppliedArgs) =>
this.inputNames.map(name => {
const type = this.funcParams[name].type;
//TODO: parse args based on type

View File

@ -6,6 +6,11 @@ const ABIFUNC_METHOD_NAMES = ['encodeInput', 'decodeInput', 'decodeOutput'];
enum ABIMethodTypes {
FUNC = 'function'
}
export default interface Contract {
[key: string]: any;
};
export type TContract = typeof Contract;
export default class Contract {
@ -22,14 +27,14 @@ export default class Contract {
return isFunc ? { ...accu, [currContractMethodName]: currContractMethod } : accu;
}, {});
public abi;
public abi: any;
constructor(abi, outputMappings: ContractOutputMappings = {}) {
constructor(abi: any, outputMappings: ContractOutputMappings = {}) {
this.assignABIFuncs(abi, outputMappings);
}
private assignABIFuncs = (abi, outputMappings: ContractOutputMappings) => {
abi.forEach(currentABIMethod => {
private assignABIFuncs = (abi: any, outputMappings: ContractOutputMappings) => {
abi.forEach((currentABIMethod: any) => {
const { name, type } = currentABIMethod;
if (type === ABIMethodTypes.FUNC) {
//only grab the functions we need

View File

@ -36,3 +36,11 @@ export interface FuncParams {
processInput(value: any): any;
};
}
export interface ITypeMapping {
[type: string]: (value: any) => any;
}
export interface ISuppliedArgs {
[argumentName: string]: any;
}

View File

@ -1,7 +1,7 @@
import { mnemonicToSeed, validateMnemonic } from 'bip39';
import { createDecipheriv, createHash } from 'crypto';
import { privateToAddress } from 'ethereumjs-util';
import { fromMasterSeed } from 'hdkey';
import HDkey from 'hdkey';
import { stripHexPrefixAndLower } from 'libs/values';
// adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L230
@ -37,7 +37,7 @@ export function decodeCryptojsSalt(input: string): any {
export function evp_kdf(data: Buffer, salt: Buffer, opts: any) {
// A single EVP iteration, returns `D_i`, where block equlas to `D_(i-1)`
function iter(block) {
function iter(block: Buffer) {
let hash = createHash(opts.digest || 'md5');
hash.update(block);
hash.update(data);
@ -83,7 +83,7 @@ export function decryptMnemonicToPrivKey(
}
const seed = mnemonicToSeed(phrase, pass);
const derived = fromMasterSeed(seed).derive(path);
const derived = HDkey.fromMasterSeed(seed).derive(path);
const dPrivKey = derived.privateKey;
const dAddress = privateToAddress(dPrivKey).toString('hex');

View File

@ -1,7 +1,7 @@
import uts46 from 'idna-uts46';
import ethUtil from 'ethereumjs-util';
export function normalise(name: string) {
export function normalise(name: string): string {
try {
return uts46.toUnicode(name, { useStd3ASCII: true, transitional: false });
} catch (e) {
@ -65,7 +65,7 @@ export enum NameState {
NotYetAvailable = '5'
}
export const modeStrMap = name => [
export const modeStrMap = (name: string) => [
`${name} is available and the auction hasnt started`,
`${name} is available and the auction has been started`,
`${name} is taken and currently owned by someone`,

View File

@ -11,6 +11,7 @@ import {
GetCurrentBlockRequest
} from './types';
import { Token } from 'types/network';
import { IHexStrWeb3Transaction, IHexStrTransaction } from 'libs/transaction';
export default class EtherscanRequests extends RPCRequests {
public sendRawTx(signedTx: string): SendRawTxRequest {
@ -21,7 +22,7 @@ export default class EtherscanRequests extends RPCRequests {
};
}
public estimateGas(transaction): EstimateGasRequest {
public estimateGas(transaction: IHexStrWeb3Transaction): EstimateGasRequest {
return {
module: 'proxy',
action: 'eth_estimateGas',
@ -41,7 +42,7 @@ export default class EtherscanRequests extends RPCRequests {
};
}
public ethCall(transaction): CallRequest {
public ethCall(transaction: Pick<IHexStrTransaction, 'to' | 'data'>): CallRequest {
return {
module: 'proxy',
action: 'eth_call',

View File

@ -41,10 +41,10 @@ export default class RPCClient {
}).then(r => r.json());
};
private createHeaders = headerObject => {
private createHeaders = (headerObject: HeadersInit) => {
const headers = new Headers();
Object.keys(headerObject).forEach(name => {
headers.append(name, headerObject[name]);
Object.entries(headerObject).forEach(([name, value]) => {
headers.append(name, value);
});
return headers;
};

View File

@ -2,7 +2,6 @@ import BN from 'bn.js';
import { IHexStrTransaction } from 'libs/transaction';
import { Wei, TokenValue } from 'libs/units';
import { stripHexPrefix } from 'libs/values';
import { hexToNumber } from 'utils/formatters';
import { INode, TxObj, TransactionData, TransactionReceipt } from '../INode';
import RPCClient from './client';
import RPCRequests from './requests';
@ -18,6 +17,7 @@ import {
isValidRawTxApi
} from 'libs/validators';
import { Token } from 'types/network';
import { hexToNumber } from 'utils/formatters';
export default class RpcNode implements INode {
public client: RPCClient;

View File

@ -13,14 +13,13 @@ import {
import { hexEncodeData } from './utils';
import { TxObj } from '../INode';
import { Token } from 'types/network';
import { IHexStrTransaction } from 'libs/transaction';
export default class RPCRequests {
public getNetVersion() {
return { method: 'net_version' };
}
/* TODO: Fix `| any` on all of these */
public sendRawTx(signedTx: string): SendRawTxRequest | any {
return {
method: 'eth_sendRawTransaction',
@ -28,7 +27,7 @@ export default class RPCRequests {
};
}
public estimateGas(transaction): EstimateGasRequest | any {
public estimateGas(transaction: Partial<IHexStrTransaction>): EstimateGasRequest | any {
return {
method: 'eth_estimateGas',
params: [transaction]

View File

@ -27,7 +27,7 @@ export interface GetAccountsRequest extends RPCRequestBase {
method: 'eth_accounts';
}
type TWeb3ProviderCallback = (error, result: JsonRpcResponse | JsonRpcResponse[]) => any;
type TWeb3ProviderCallback = (error: string, result: JsonRpcResponse | JsonRpcResponse[]) => any;
type TSendAsync = (request: RPCRequest | any, callback: TWeb3ProviderCallback) => void;
export interface IWeb3Provider {

View File

@ -10,9 +10,9 @@ export interface ITransaction {
gasPrice: Wei;
nonce: BN;
chainId: number;
v;
r;
s;
v: Buffer;
r: Buffer;
s: Buffer;
}
export interface IHexStrTransaction {

View File

@ -6,6 +6,7 @@ import { IFullWallet } from 'libs/wallet';
import { translateRaw } from 'translations';
import { ITransaction, IHexStrTransaction } from '../typings';
import { hexEncodeQuantity, hexEncodeData } from 'libs/nodes/rpc/utils';
import { TransactionFieldValues } from 'selectors/transaction/helpers';
// we dont include the signature paramaters because web3 transactions are unsigned
const computeIndexingHash = (tx: Buffer) => bufferToHex(makeTransaction(tx).hash(false));
@ -75,7 +76,13 @@ const validAddress = (t: ITransaction) => {
};
const makeTransaction = (
t: Partial<Tx> | Partial<ITransaction> | Partial<IHexStrTransaction> | Buffer | string
t:
| Partial<Tx>
| Partial<ITransaction>
| Partial<IHexStrTransaction>
| Buffer
| string
| TransactionFieldValues
) => new Tx(t);
//TODO: check that addresses are always checksummed

View File

@ -184,7 +184,7 @@ export const isValidNonce = (value: string): boolean => {
return valid;
};
function isValidResult(response: JsonRpcResponse, schemaFormat): boolean {
function isValidResult(response: JsonRpcResponse, schemaFormat: typeof schema.RpcNode): boolean {
return v.validate(response, schemaFormat).valid;
}
@ -204,9 +204,25 @@ function formatErrors(response: JsonRpcResponse, apiType: string) {
return `Invalid ${apiType} Error`;
}
enum API_NAME {
Get_Balance = 'Get Balance',
Estimate_Gas = 'Estimate Gas',
Call_Request = 'Call Request',
Token_Balance = 'Token Balance',
Transaction_Count = 'Transaction Count',
Current_Block = 'Current Block',
Raw_Tx = 'Raw Tx',
Send_Transaction = 'Send Transaction',
Sign_Message = 'Sign Message',
Get_Accounts = 'Get Accounts',
Net_Version = 'Net Version',
Transaction_By_Hash = 'Transaction By Hash',
Transaction_Receipt = 'Transaction Receipt'
}
const isValidEthCall = (response: JsonRpcResponse, schemaType: typeof schema.RpcNode) => (
apiName,
cb?
apiName: API_NAME,
cb?: (res: JsonRpcResponse) => any
) => {
if (!isValidResult(response, schemaType)) {
if (cb) {
@ -218,45 +234,44 @@ const isValidEthCall = (response: JsonRpcResponse, schemaType: typeof schema.Rpc
};
export const isValidGetBalance = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Get Balance');
isValidEthCall(response, schema.RpcNode)(API_NAME.Get_Balance);
export const isValidEstimateGas = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Estimate Gas');
isValidEthCall(response, schema.RpcNode)(API_NAME.Estimate_Gas);
export const isValidCallRequest = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Call Request');
isValidEthCall(response, schema.RpcNode)(API_NAME.Call_Request);
export const isValidTokenBalance = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Token Balance', () => ({
isValidEthCall(response, schema.RpcNode)(API_NAME.Token_Balance, () => ({
result: 'Failed'
}));
export const isValidTransactionCount = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Transaction Count');
isValidEthCall(response, schema.RpcNode)(API_NAME.Transaction_Count);
export const isValidTransactionByHash = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Transaction By Hash');
isValidEthCall(response, schema.RpcNode)(API_NAME.Transaction_By_Hash);
export const isValidTransactionReceipt = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Transaction Receipt');
isValidEthCall(response, schema.RpcNode)(API_NAME.Transaction_Receipt);
export const isValidCurrentBlock = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Current Block');
isValidEthCall(response, schema.RpcNode)(API_NAME.Current_Block);
export const isValidRawTxApi = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Raw Tx');
isValidEthCall(response, schema.RpcNode)(API_NAME.Raw_Tx);
export const isValidSendTransaction = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Send Transaction');
isValidEthCall(response, schema.RpcNode)(API_NAME.Send_Transaction);
export const isValidSignMessage = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Sign Message');
isValidEthCall(response, schema.RpcNode)(API_NAME.Sign_Message);
export const isValidGetAccounts = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Get Accounts');
isValidEthCall(response, schema.RpcNode)(API_NAME.Get_Accounts);
export const isValidGetNetVersion = (response: JsonRpcResponse) =>
isValidEthCall(response, schema.RpcNode)('Net Version');
isValidEthCall(response, schema.RpcNode)(API_NAME.Net_Version);
export const isValidTxHash = (hash: string) =>
hash.substring(0, 2) === '0x' && hash.length === 66 && isValidHex(hash);

View File

@ -1,6 +1,6 @@
import { Wei, toTokenBase } from 'libs/units';
import { addHexPrefix } from 'ethereumjs-util';
import BN from 'bn.js';
import { AppState } from 'reducers';
export function stripHexPrefix(value: string) {
return value.replace('0x', '');
@ -26,16 +26,16 @@ export function sanitizeHex(hex: string) {
export const buildEIP681EtherRequest = (
recipientAddr: string,
chainId: number,
etherValue: { raw: string; value: Wei | '' }
etherValue: AppState['transaction']['fields']['value']
) => `ethereum:${recipientAddr}${chainId !== 1 ? `@${chainId}` : ''}?value=${etherValue.raw}e18`;
export const buildEIP681TokenRequest = (
recipientAddr: string,
contractAddr: string,
chainId: number,
tokenValue: { raw: string; value: Wei | '' },
tokenValue: AppState['transaction']['meta']['tokenTo'],
decimal: number,
gasLimit: { raw: string; value: BN | null }
gasLimit: AppState['transaction']['fields']['gasLimit']
) =>
`ethereum:${contractAddr}${
chainId !== 1 ? `@${chainId}` : ''

View File

@ -7,11 +7,11 @@ import { IFullWallet } from '../IWallet';
import { translateRaw } from 'translations';
export class LedgerWallet extends DeterministicWallet implements IFullWallet {
private ethApp: any;
private ethApp: ledger.eth;
constructor(address: string, dPath: string, index: number) {
super(address, dPath, index);
ledger.comm_u2f.create_async().then(comm => {
ledger.comm_u2f.create_async().then((comm: any) => {
this.ethApp = new ledger.eth(comm);
});
}
@ -50,18 +50,22 @@ export class LedgerWallet extends DeterministicWallet implements IFullWallet {
const msgHex = Buffer.from(msg).toString('hex');
return new Promise((resolve, reject) => {
this.ethApp.signPersonalMessage_async(this.getPath(), msgHex, async (signed, error) => {
if (error) {
return reject(this.ethApp.getError(error));
}
this.ethApp.signPersonalMessage_async(
this.getPath(),
msgHex,
async (signed: any, error: any) => {
if (error) {
return reject((this.ethApp as any).getError(error));
}
try {
const combined = signed.r + signed.s + signed.v;
resolve(bufferToHex(combined));
} catch (err) {
reject(err);
try {
const combined = signed.r + signed.s + signed.v;
resolve(bufferToHex(combined));
} catch (err) {
reject(err);
}
}
});
);
});
}

View File

@ -30,7 +30,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
cleanedTx.data,
chainId,
// Callback
result => {
(result: any) => {
if (!result.success) {
return reject(Error(result.error));
}
@ -64,7 +64,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
return new Promise((resolve, reject) => {
(TrezorConnect as any).ethereumGetAddress(
dPath + '/' + index,
res => {
(res: any) => {
if (res.error) {
reject(res.error);
} else {

View File

@ -15,9 +15,9 @@ const INITIAL_STATE: State = {
gasPriceStatus: null
};
const getPostFix = (str: string) => {
const getPostFix = (str: string): keyof typeof RequestStatus => {
const arr = str.split('_');
return arr[arr.length - 1];
return arr[arr.length - 1] as any;
};
const nextState = (field: keyof State) => (state: State, action: Action): State => ({

View File

@ -22,14 +22,14 @@ export const resetHOF = (
if (includeFields) {
(includeFields as any[]).forEach(fieldName => {
stateCopy[fieldName] = returnState[fieldName];
(stateCopy as any)[fieldName] = (returnState as any)[fieldName];
});
return returnCb ? returnCb(state, returnState) : { ...stateCopy };
}
if (excludeFields) {
(excludeFields as any[]).forEach(fieldName => {
returnState[fieldName] = state[fieldName];
(returnState as any)[fieldName] = (state as any)[fieldName];
});
}

View File

@ -15,7 +15,7 @@ export function* pruneCustomNetworks(): SagaIterator {
//construct lookup table of networks
const linkedNetworks = Object.values(customNodes).reduce(
const linkedNetworks: { [key: string]: boolean } = Object.values(customNodes).reduce(
(networkMap, currentNode) => ({ ...networkMap, [currentNode.network]: true }),
{}
);

View File

@ -6,6 +6,7 @@ import { changeNodeForce, TypeKeys, web3SetNode } from 'actions/config';
import { getNodeId, getStaticAltNodeIdToWeb3, getNetworkNameByChainId } from 'selectors/config';
import { setupWeb3Node, Web3Service } from 'libs/nodes/web3';
import { Web3NodeConfig } from 'types/node';
import { SetWalletAction } from 'actions/wallet';
export function* initWeb3Node(): SagaIterator {
const { networkId, lib } = yield call(setupWeb3Node);
@ -24,7 +25,7 @@ export function* initWeb3Node(): SagaIterator {
}
// unset web3 as the selected node if a non-web3 wallet has been selected
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
export function* unsetWeb3NodeOnWalletEvent(action: SetWalletAction): SagaIterator {
const node = yield select(getNodeId);
const newWallet = action.payload;
const isWeb3Wallet = newWallet instanceof Web3Wallet;
@ -52,6 +53,5 @@ export function* unsetWeb3Node(): SagaIterator {
export const web3 = [
takeEvery(TypeKeys.CONFIG_NODE_WEB3_UNSET, unsetWeb3Node),
takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3NodeOnWalletEvent),
takeEvery(WalletTypeKeys.WALLET_RESET, unsetWeb3NodeOnWalletEvent)
takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3NodeOnWalletEvent)
];

Some files were not shown because too many files have changed in this diff Show More