mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-02-10 10:07:00 +00:00
Merge branch 'develop' into develop
This commit is contained in:
commit
71c76e8afc
125
common/api/shapeshift.spec.ts
Normal file
125
common/api/shapeshift.spec.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import shapeshift, { SHAPESHIFT_BASE_URL } from './shapeshift';
|
||||
|
||||
describe('ShapeShift service', () => {
|
||||
beforeEach(() => {
|
||||
(global as any).fetch = jest.fn().mockImplementation(
|
||||
(url: string) =>
|
||||
new Promise(resolve => {
|
||||
const returnValues = {
|
||||
[`${SHAPESHIFT_BASE_URL}/marketinfo`]: {
|
||||
status: 200,
|
||||
json: () => [
|
||||
{
|
||||
limit: 1,
|
||||
maxLimit: 2,
|
||||
min: 1,
|
||||
minerFee: 2,
|
||||
pair: 'BTC_ETH',
|
||||
rate: '1.0'
|
||||
},
|
||||
{
|
||||
limit: 1,
|
||||
maxLimit: 2,
|
||||
min: 1,
|
||||
minerFee: 2,
|
||||
pair: 'ETH_BTC',
|
||||
rate: '1.0'
|
||||
}
|
||||
]
|
||||
},
|
||||
[`${SHAPESHIFT_BASE_URL}/getcoins`]: {
|
||||
status: 200,
|
||||
json: () => ({
|
||||
BTC: {
|
||||
name: 'Bitcoin',
|
||||
symbol: 'BTC',
|
||||
image: '',
|
||||
imageSmall: '',
|
||||
status: 'available',
|
||||
minerFee: 1
|
||||
},
|
||||
ETH: {
|
||||
name: 'Ethereum',
|
||||
symbol: 'ETH',
|
||||
image: '',
|
||||
imageSmall: '',
|
||||
status: 'available',
|
||||
minerFee: 1
|
||||
},
|
||||
XMR: {
|
||||
name: 'Monero',
|
||||
symbol: 'XMR',
|
||||
image: '',
|
||||
imageSmall: '',
|
||||
status: 'unavailable',
|
||||
minerFee: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
resolve(returnValues[url]);
|
||||
})
|
||||
);
|
||||
});
|
||||
it('provides a collection of all available and unavailable coins and tokens', async done => {
|
||||
const rates = await shapeshift.getAllRates();
|
||||
|
||||
expect(rates).toEqual({
|
||||
BTCETH: {
|
||||
id: 'BTCETH',
|
||||
rate: '1.0',
|
||||
limit: 1,
|
||||
min: 1,
|
||||
options: [
|
||||
{
|
||||
id: 'BTC',
|
||||
image: '',
|
||||
name: 'Bitcoin',
|
||||
status: 'available'
|
||||
},
|
||||
{
|
||||
id: 'ETH',
|
||||
image: '',
|
||||
name: 'Ethereum',
|
||||
status: 'available'
|
||||
}
|
||||
]
|
||||
},
|
||||
ETHBTC: {
|
||||
id: 'ETHBTC',
|
||||
rate: '1.0',
|
||||
limit: 1,
|
||||
min: 1,
|
||||
options: [
|
||||
{
|
||||
id: 'ETH',
|
||||
image: '',
|
||||
name: 'Ethereum',
|
||||
status: 'available'
|
||||
},
|
||||
{
|
||||
id: 'BTC',
|
||||
image: '',
|
||||
name: 'Bitcoin',
|
||||
status: 'available'
|
||||
}
|
||||
]
|
||||
},
|
||||
__XMR: {
|
||||
id: '__XMR',
|
||||
limit: 0,
|
||||
min: 0,
|
||||
options: [
|
||||
{
|
||||
id: 'XMR',
|
||||
image: '',
|
||||
name: 'Monero',
|
||||
status: 'unavailable'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
@ -1,9 +1,12 @@
|
||||
import flatten from 'lodash/flatten';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
|
||||
import { checkHttpStatus, parseJSON } from 'api/utils';
|
||||
|
||||
const SHAPESHIFT_API_KEY =
|
||||
export const SHAPESHIFT_API_KEY =
|
||||
'8abde0f70ca69d5851702d57b10305705d7333e93263124cc2a2649dab7ff9cf86401fc8de7677e8edcd0e7f1eed5270b1b49be8806937ef95d64839e319e6d9';
|
||||
|
||||
const SHAPESHIFT_BASE_URL = 'https://shapeshift.io';
|
||||
export const SHAPESHIFT_BASE_URL = 'https://shapeshift.io';
|
||||
|
||||
export const SHAPESHIFT_TOKEN_WHITELIST = [
|
||||
'OMG',
|
||||
@ -26,7 +29,7 @@ export const SHAPESHIFT_TOKEN_WHITELIST = [
|
||||
'TRST',
|
||||
'GUP'
|
||||
];
|
||||
export const SHAPESHIFT_WHITELIST = [...SHAPESHIFT_TOKEN_WHITELIST, 'ETH', 'ETC', 'BTC'];
|
||||
export const SHAPESHIFT_WHITELIST = [...SHAPESHIFT_TOKEN_WHITELIST, 'ETH', 'ETC', 'BTC', 'XMR'];
|
||||
|
||||
interface IPairData {
|
||||
limit: number;
|
||||
@ -66,6 +69,29 @@ interface TokenMap {
|
||||
};
|
||||
}
|
||||
|
||||
interface ShapeshiftCoinInfo {
|
||||
image: string;
|
||||
imageSmall: string;
|
||||
minerFee: number;
|
||||
name: string;
|
||||
status: string;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
interface ShapeshiftCoinInfoMap {
|
||||
[id: string]: ShapeshiftCoinInfo;
|
||||
}
|
||||
|
||||
interface ShapeshiftOption {
|
||||
id?: string;
|
||||
status?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface ShapeshiftOptionMap {
|
||||
[symbol: string]: ShapeshiftOption;
|
||||
}
|
||||
|
||||
class ShapeshiftService {
|
||||
public whitelist = SHAPESHIFT_WHITELIST;
|
||||
private url = SHAPESHIFT_BASE_URL;
|
||||
@ -73,6 +99,8 @@ class ShapeshiftService {
|
||||
private postHeaders = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
private supportedCoinsAndTokens: ShapeshiftCoinInfoMap = {};
|
||||
private fetchedSupportedCoinsAndTokens = false;
|
||||
|
||||
public checkStatus(address: string) {
|
||||
return fetch(`${this.url}/txStat/${address}`)
|
||||
@ -118,19 +146,76 @@ class ShapeshiftService {
|
||||
|
||||
public getAllRates = async () => {
|
||||
const marketInfo = await this.getMarketInfo();
|
||||
const pairRates = await this.filterPairs(marketInfo);
|
||||
const pairRates = this.filterPairs(marketInfo);
|
||||
const checkAvl = await this.checkAvl(pairRates);
|
||||
const mappedRates = this.mapMarketInfo(checkAvl);
|
||||
return mappedRates;
|
||||
const allRates = this.addUnavailableCoinsAndTokens(mappedRates);
|
||||
|
||||
return allRates;
|
||||
};
|
||||
|
||||
public addUnavailableCoinsAndTokens = (availableCoinsAndTokens: TokenMap) => {
|
||||
if (this.fetchedSupportedCoinsAndTokens) {
|
||||
/** @desc Create a hash for efficiently checking which tokens are currently available. */
|
||||
const allOptions = flatten(
|
||||
Object.values(availableCoinsAndTokens).map(({ options }) => options)
|
||||
);
|
||||
const availableOptions: ShapeshiftOptionMap = uniqBy(allOptions, 'id').reduce(
|
||||
(prev: ShapeshiftOptionMap, next) => {
|
||||
prev[next.id] = next;
|
||||
return prev;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const unavailableCoinsAndTokens = this.whitelist
|
||||
.map(token => {
|
||||
/** @desc ShapeShift claims support for the token and it is available. */
|
||||
const availableCoinOrToken = availableOptions[token];
|
||||
|
||||
if (availableCoinOrToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @desc ShapeShift claims support for the token, but it is unavailable. */
|
||||
const supportedCoinOrToken = this.supportedCoinsAndTokens[token];
|
||||
|
||||
if (supportedCoinOrToken) {
|
||||
const { symbol: id, image, name, status } = supportedCoinOrToken;
|
||||
|
||||
return {
|
||||
/** @desc Preface the false id with '__' to differentiate from actual pairs. */
|
||||
id: `__${id}`,
|
||||
limit: 0,
|
||||
min: 0,
|
||||
options: [{ id, image, name, status }]
|
||||
};
|
||||
}
|
||||
|
||||
/** @desc We claim support for the coin or token, but ShapeShift doesn't. */
|
||||
return null;
|
||||
})
|
||||
.reduce((prev: ShapeshiftOptionMap, next) => {
|
||||
if (next) {
|
||||
prev[next.id] = next;
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
return { ...availableCoinsAndTokens, ...unavailableCoinsAndTokens };
|
||||
}
|
||||
|
||||
return availableCoinsAndTokens;
|
||||
};
|
||||
|
||||
private filterPairs(marketInfo: ShapeshiftMarketInfo[]) {
|
||||
return marketInfo.filter(obj => {
|
||||
const { pair } = obj;
|
||||
const pairArr = pair.split('_');
|
||||
return this.whitelist.includes(pairArr[0]) && this.whitelist.includes(pairArr[1])
|
||||
? true
|
||||
: false;
|
||||
return this.whitelist.includes(pairArr[0]) && this.whitelist.includes(pairArr[1]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -165,7 +250,13 @@ class ShapeshiftService {
|
||||
private getAvlCoins() {
|
||||
return fetch(`${this.url}/getcoins`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
.then(parseJSON)
|
||||
.then(supportedCoinsAndTokens => {
|
||||
this.supportedCoinsAndTokens = supportedCoinsAndTokens;
|
||||
this.fetchedSupportedCoinsAndTokens = true;
|
||||
|
||||
return supportedCoinsAndTokens;
|
||||
});
|
||||
}
|
||||
|
||||
private getMarketInfo() {
|
||||
|
135
common/assets/images/wallets/safe-t.svg
Normal file
135
common/assets/images/wallets/safe-t.svg
Normal file
@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="217.19"
|
||||
height="252"
|
||||
viewBox="0 0 217.19 252"
|
||||
version="1.1"
|
||||
id="svg4200"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="safe-t.svg">
|
||||
<metadata
|
||||
id="metadata4215">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>trezor</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1381"
|
||||
id="namedview4213"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.8294574"
|
||||
inkscape:cx="153.14158"
|
||||
inkscape:cy="62.034553"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4200" />
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title
|
||||
id="title4202">trezor</title>
|
||||
<desc
|
||||
id="desc4204">Created with Sketch.</desc>
|
||||
<defs
|
||||
id="defs4206">
|
||||
<clipPath
|
||||
id="presentation_clip_path"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="21000"
|
||||
height="29700"
|
||||
id="rect7" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
id="Page-1"
|
||||
style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1"
|
||||
transform="translate(0,-6)">
|
||||
<g
|
||||
id="trezor"
|
||||
style="fill:#000000;fill-rule:nonzero">
|
||||
<g
|
||||
id="path7" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.03542311,0,0,0.03542311,-244.40864,-399.82064)"
|
||||
class="SlideGroup"
|
||||
id="g49">
|
||||
<g
|
||||
id="g51">
|
||||
<g
|
||||
id="id1"
|
||||
class="Slide"
|
||||
clip-path="url(#presentation_clip_path)">
|
||||
<g
|
||||
class="Page"
|
||||
id="g54">
|
||||
<g
|
||||
class="com.sun.star.drawing.ClosedBezierShape"
|
||||
id="g56">
|
||||
<g
|
||||
id="id3">
|
||||
<rect
|
||||
style="fill:none;stroke:none"
|
||||
class="BoundingBox"
|
||||
x="6900"
|
||||
y="13921"
|
||||
width="6114"
|
||||
height="4480"
|
||||
id="rect59" />
|
||||
<path
|
||||
style="fill:#000000;stroke:none"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 12880,15423 c 256,0 61,638 61,638 l 0,0 c -9,29 -20,53 -35,79 -4,8 -8,14 -13,22 -402,602 -2104,1772 -2705,2173 -7,5 -14,9 -22,14 -60,35 -120,51 -189,51 -70,0 -130,-16 -190,-51 -8,-5 -15,-9 -22,-14 -600,-401 -2303,-1571 -2705,-2173 -4,-8 -8,-14 -12,-21 -22,-37 -35,-71 -45,-112 -95,-400 -117,-879 -96,-1372 l 48,-735 c 0,0 1690,1634 5925,1501 z"
|
||||
id="path61" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
class="com.sun.star.drawing.ClosedBezierShape"
|
||||
id="g63">
|
||||
<g
|
||||
id="id4">
|
||||
<rect
|
||||
style="fill:none;stroke:none"
|
||||
class="BoundingBox"
|
||||
x="7033"
|
||||
y="11287"
|
||||
width="5998"
|
||||
height="3839"
|
||||
id="rect66" />
|
||||
<path
|
||||
style="fill:#000000;stroke:none"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 13029,14494 c -77,-1049 -318,-2109 -437,-2592 l 0,0 c -9,-37 -21,-67 -40,-99 -35,-61 -79,-105 -140,-140 -47,-27 -91,-42 -145,-49 -587,-70 -1493,-327 -2283,-327 l -12,0 c -789,0 -1694,256 -2281,327 -55,7 -100,22 -147,49 -61,36 -105,80 -141,141 -19,33 -31,63 -40,99 -83,335 -223,944 -327,1635 l 0,0 c -2,12 -3,23 -3,36 0,45 10,84 33,123 20,34 43,59 76,81 345,234 1322,801 2711,1122 1021,236 2057,283 2745,153 62,-12 113,-31 168,-62 84,-49 145,-109 193,-194 49,-84 71,-167 71,-265 0,-13 0,-24 -1,-38 z m -2621,-1387 0,0 c -21,19 -36,39 -51,64 -25,43 -36,86 -36,136 0,2 0,3 0,5 l 0,687 0,0 c 0,32 -8,58 -23,86 -16,27 -35,46 -62,62 -28,15 -54,23 -86,23 l -235,0 0,0 c -32,0 -58,-8 -85,-23 -27,-16 -47,-35 -62,-62 -16,-28 -23,-54 -23,-86 l 0,-692 0,0 c -1,-50 -13,-92 -38,-136 -16,-28 -34,-49 -58,-70 l 0,0 c -46,-42 -79,-84 -110,-138 -53,-91 -77,-180 -77,-285 0,-105 24,-194 77,-285 52,-91 118,-156 209,-209 91,-53 180,-76 285,-76 105,0 194,23 285,76 91,53 156,118 209,209 52,91 76,180 76,285 0,105 -24,194 -76,285 -33,57 -68,101 -118,144 l -1,0 z"
|
||||
id="path68" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
9
common/badBrowserCheckA.js
Normal file
9
common/badBrowserCheckA.js
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
// use dates as vars so they're not stripped out by minification
|
||||
let letCheck = new Date();
|
||||
const constCheck = new Date();
|
||||
const arrowCheck = (() => new Date())();
|
||||
|
||||
if (letCheck && constCheck && arrowCheck) {
|
||||
window.localStorage.setItem('goodBrowser', 'true');
|
||||
}
|
33
common/badBrowserCheckB.js
Normal file
33
common/badBrowserCheckB.js
Normal file
@ -0,0 +1,33 @@
|
||||
var badBrowser = false;
|
||||
|
||||
try {
|
||||
// Local storage
|
||||
window.localStorage.setItem('test', 'test');
|
||||
window.localStorage.removeItem('test');
|
||||
|
||||
// Flexbox
|
||||
var elTest = document.createElement('div');
|
||||
elTest.style.display = 'flex';
|
||||
if (elTest.style.display !== 'flex') {
|
||||
badBrowser = true;
|
||||
}
|
||||
|
||||
// const and let check from badBrowserCheckA.js
|
||||
if (window.localStorage.goodBrowser !== 'true') {
|
||||
badBrowser = true;
|
||||
}
|
||||
window.localStorage.removeItem('goodBrowser');
|
||||
|
||||
} catch (err) {
|
||||
badBrowser = true;
|
||||
}
|
||||
|
||||
if (badBrowser) {
|
||||
var el = document.getElementsByClassName('BadBrowser')[0];
|
||||
el.className += ' is-open';
|
||||
// Dumb check for known mobile OS's. Not important to catch all, just
|
||||
// displays more appropriate information.
|
||||
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
||||
el.className += ' is-mobile';
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ interface Props {
|
||||
hasUnitDropdown?: boolean;
|
||||
hasSendEverything?: boolean;
|
||||
showAllTokens?: boolean;
|
||||
showInvalidWithoutValue?: boolean;
|
||||
customValidator?(rawAmount: string): boolean;
|
||||
}
|
||||
|
||||
@ -16,7 +17,8 @@ export const AmountField: React.SFC<Props> = ({
|
||||
hasUnitDropdown,
|
||||
hasSendEverything,
|
||||
showAllTokens,
|
||||
customValidator
|
||||
customValidator,
|
||||
showInvalidWithoutValue
|
||||
}) => (
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
@ -30,6 +32,7 @@ export const AmountField: React.SFC<Props> = ({
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
showInvalidWithoutValue={showInvalidWithoutValue}
|
||||
/>
|
||||
{hasSendEverything && <SendEverything />}
|
||||
{hasUnitDropdown && <UnitDropDown showAllTokens={showAllTokens} />}
|
||||
|
@ -1,53 +0,0 @@
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.AppAlpha {
|
||||
@include cover-message;
|
||||
background: color(brand-info);
|
||||
left: $electron-sidebar-width - 1;
|
||||
|
||||
&-content {
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
&-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
margin: 40px auto 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
transition: $transition;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 22px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
opacity: 0.96;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fade out
|
||||
&.is-fading {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
background: color(control-bg);
|
||||
transition: all 500ms ease 400ms;
|
||||
|
||||
.AppAlpha-content {
|
||||
opacity: 0;
|
||||
transform: translateY(15px);
|
||||
transition: all 500ms ease;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import { discordURL, APP_ALPHA_EXPIRATION } from 'config';
|
||||
import { NewTabLink } from 'components/ui';
|
||||
import './AlphaNotice.scss';
|
||||
|
||||
interface State {
|
||||
isFading: boolean;
|
||||
isClosed: boolean;
|
||||
}
|
||||
|
||||
let hasAcknowledged = false;
|
||||
|
||||
export default class AppAlphaNotice extends React.PureComponent<{}, State> {
|
||||
public state = {
|
||||
isFading: false,
|
||||
isClosed: hasAcknowledged
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (this.state.isClosed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isFading = this.state.isFading ? 'is-fading' : '';
|
||||
const expDate = moment(APP_ALPHA_EXPIRATION).format('MMMM Do, YYYY');
|
||||
|
||||
return (
|
||||
<div className={`AppAlpha ${isFading}`}>
|
||||
<div className="AppAlpha-content">
|
||||
<h2>Welcome to the MyCrypto Desktop App Alpha</h2>
|
||||
<p>
|
||||
Thank you for testing out the new MyCrypto desktop app. This is an early release to be
|
||||
tested by the community before a full launch. We recommend continuing to use the
|
||||
production site for large or otherwise important transactions.
|
||||
</p>
|
||||
<p>
|
||||
Because this is for testing purposes only,{' '}
|
||||
<strong>this build of the app will only be accessible until {expDate}</strong>. You’ll
|
||||
then be required to update the application to continue using it.
|
||||
</p>
|
||||
<p>
|
||||
Feedback and bug reports are greatly appreciated. You can file issues on our{' '}
|
||||
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">
|
||||
GitHub repository
|
||||
</NewTabLink>{' '}
|
||||
or join our <NewTabLink href={discordURL}>Discord server</NewTabLink> to discuss the
|
||||
app.
|
||||
</p>
|
||||
<p>
|
||||
<b>
|
||||
For critical reports & vulnerabilities, please use{' '}
|
||||
<NewTabLink href="https://hackerone.com/MyCrypto">HackerOne</NewTabLink>.
|
||||
</b>
|
||||
</p>
|
||||
|
||||
<button className="AppAlpha-content-btn is-continue" onClick={this.doContinue}>
|
||||
Continue to the Alpha
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private doContinue = () => {
|
||||
hasAcknowledged = true;
|
||||
this.setState({ isFading: true });
|
||||
setTimeout(() => {
|
||||
this.setState({ isClosed: true });
|
||||
}, 1000);
|
||||
};
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.AppExpired {
|
||||
@include cover-message;
|
||||
background: color(brand-danger);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-content {
|
||||
padding-bottom: 60px;
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
&-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
margin: 40px auto 0;
|
||||
text-align: center;
|
||||
border: none;
|
||||
padding: 0;
|
||||
transition: $transition;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 22px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border-radius: 4px;
|
||||
text-shadow: none;
|
||||
opacity: 0.95;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { NewTabLink } from 'components/ui';
|
||||
import './AppExpired.scss';
|
||||
|
||||
const AppExpired: React.SFC<{}> = () => (
|
||||
<div className="AppExpired">
|
||||
<div className="AppExpired-content">
|
||||
<h2>Your Alpha Build Has Expired</h2>
|
||||
<p>
|
||||
To ensure the safety of your funds, we are expiring alpha builds one month after release and
|
||||
requiring users to update. All you have to do is download a new build from our GitHub, and
|
||||
you can continue to use the app. Sorry for the hassle!
|
||||
</p>
|
||||
|
||||
<NewTabLink
|
||||
href="https://github.com/MyCryptoHQ/MyCrypto/releases/latest"
|
||||
className="AppExpired-content-btn"
|
||||
>
|
||||
Download a New Build
|
||||
</NewTabLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default AppExpired;
|
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { APP_ALPHA_EXPIRATION } from 'config';
|
||||
import AlphaNotice from './AlphaNotice';
|
||||
import AppExpired from './AppExpired';
|
||||
|
||||
const AppAlphaNotice: React.SFC<{}> = () => {
|
||||
if (APP_ALPHA_EXPIRATION < Date.now()) {
|
||||
return <AppExpired />;
|
||||
} else {
|
||||
return <AlphaNotice />;
|
||||
}
|
||||
};
|
||||
|
||||
export default AppAlphaNotice;
|
@ -13,17 +13,7 @@ const CarouselAnimation = ({ children, ...props }: any) => (
|
||||
);
|
||||
|
||||
// Don't change Coinbase index
|
||||
const promos = [HardwareWallets, Coinbase, Shapeshift];
|
||||
const isEuroLocal = () => {
|
||||
// getTimezoneOffset returns the difference in minutes between UTC and local time.
|
||||
// the offset is positive if behind UTC (like UTC-4), and negative if above (like UTC+2)
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
// -240 to 0 covers UTC+4 to UTC+0, which is all of europe
|
||||
return -240 <= offset && offset < 0;
|
||||
};
|
||||
if (isEuroLocal()) {
|
||||
promos.push(Simplex);
|
||||
}
|
||||
const promos = [HardwareWallets, Coinbase, Shapeshift, Simplex];
|
||||
|
||||
interface State {
|
||||
activePromo: number;
|
||||
@ -35,7 +25,6 @@ interface StateProps {
|
||||
|
||||
class PromosClass extends React.PureComponent<StateProps, State> {
|
||||
public timer: any = null;
|
||||
|
||||
public state = {
|
||||
activePromo: parseInt(String(Math.random() * promos.length), 10)
|
||||
};
|
||||
|
@ -100,6 +100,7 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
|
||||
buttons={buttons}
|
||||
handleClose={onClose}
|
||||
disableButtons={transactionBroadcasting}
|
||||
hideButtons={transactionBroadcasting}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
{transactionBroadcasting ? (
|
||||
|
@ -252,7 +252,13 @@ class CustomNodeModal extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private pollForDefaultNodes() {
|
||||
return null;
|
||||
// @ts-ignore
|
||||
const pollingInterval = 3000;
|
||||
// console.warning in production to explain to users why we are making a call to localhost
|
||||
console.warn(
|
||||
"Don't panic! MyCrypto is going to start a poll for default nodes on port 8545. If you don't like this feature, send us a ping at support@mycrypto.com and we'll walk you through disabling it."
|
||||
);
|
||||
this.timer = window.setInterval(async () => {
|
||||
const results = await exists(
|
||||
[
|
||||
|
@ -44,7 +44,6 @@ class ElectronNav extends React.Component<Props, State> {
|
||||
>
|
||||
<div className="ElectronNav-branding">
|
||||
<div className="ElectronNav-branding-logo" onClick={this.toggleTheme} />
|
||||
<div className="ElectronNav-branding-beta">Alpha Release</div>
|
||||
</div>
|
||||
|
||||
<ul className="ElectronNav-links">
|
||||
@ -54,6 +53,7 @@ class ElectronNav extends React.Component<Props, State> {
|
||||
link={link}
|
||||
isHomepage={link === navigationLinks[0]}
|
||||
className="ElectronNavLink"
|
||||
isNotEnabled={false}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -3,9 +3,11 @@ import React, { PureComponent } from 'react';
|
||||
import { navigationLinks } from 'config';
|
||||
import NavigationLink from 'components/NavigationLink';
|
||||
import './Navigation.scss';
|
||||
import { TAB } from './constants';
|
||||
|
||||
interface Props {
|
||||
color?: string | false;
|
||||
unsupportedTabs?: TAB[];
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -30,7 +32,7 @@ export default class Navigation extends PureComponent<Props, State> {
|
||||
*/
|
||||
|
||||
public render() {
|
||||
const { color } = this.props;
|
||||
const { color, unsupportedTabs } = this.props;
|
||||
const borderStyle: BorderStyle = {};
|
||||
|
||||
if (color) {
|
||||
@ -58,6 +60,9 @@ export default class Navigation extends PureComponent<Props, State> {
|
||||
link={link}
|
||||
isHomepage={link === navigationLinks[0]}
|
||||
className="NavigationLink"
|
||||
isNotEnabled={
|
||||
unsupportedTabs && unsupportedTabs.map(tab => tab.toString()).includes(link.name)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
10
common/components/Header/components/constants.ts
Normal file
10
common/components/Header/components/constants.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export enum TAB {
|
||||
VIEW = 'NAV_VIEW',
|
||||
CREATE = 'NAV_GENERATEWALLET',
|
||||
SWAP = 'NAV_SWAP',
|
||||
CONTRACTS = 'NAV_CONTRACTS',
|
||||
ENS = 'NAV_ENS',
|
||||
SIGN = 'NAV_SIGN',
|
||||
TXSTATUS = 'NAV_TXSTATUS',
|
||||
BROADCAST = 'NAV_BROADCAST'
|
||||
}
|
@ -122,7 +122,10 @@ class Header extends Component<Props, State> {
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<Navigation color={!network.isCustom && network.color} />
|
||||
<Navigation
|
||||
color={!network.isCustom && network.color}
|
||||
unsupportedTabs={network.unsupportedTabs}
|
||||
/>
|
||||
|
||||
<CustomNodeModal
|
||||
isOpen={isAddingCustomNode}
|
||||
|
@ -9,13 +9,18 @@ interface Props extends RouteComponentProps<{}> {
|
||||
link: NavigationLink;
|
||||
isHomepage: boolean;
|
||||
className: string;
|
||||
isNotEnabled?: boolean;
|
||||
}
|
||||
|
||||
class NavigationLinkClass extends React.PureComponent<Props, {}> {
|
||||
public render() {
|
||||
const { link, location, isHomepage, className } = this.props;
|
||||
const { link, location, isHomepage, className, isNotEnabled } = this.props;
|
||||
let isActive = false;
|
||||
|
||||
if (isNotEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!link.external) {
|
||||
// isActive if
|
||||
// 1) Current path is the same as link
|
||||
|
@ -12,6 +12,7 @@ import './NonceField.scss';
|
||||
|
||||
interface OwnProps {
|
||||
alwaysDisplay: boolean;
|
||||
showInvalidBeforeBlur?: boolean;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
@ -27,7 +28,13 @@ type Props = OwnProps & DispatchProps & StateProps;
|
||||
|
||||
class NonceField extends React.Component<Props> {
|
||||
public render() {
|
||||
const { alwaysDisplay, requestNonce, noncePending, isOffline } = this.props;
|
||||
const {
|
||||
alwaysDisplay,
|
||||
showInvalidBeforeBlur,
|
||||
requestNonce,
|
||||
noncePending,
|
||||
isOffline
|
||||
} = this.props;
|
||||
return (
|
||||
<NonceFieldFactory
|
||||
withProps={({ nonce: { raw, value }, onChange, readOnly, shouldDisplay }) => {
|
||||
@ -51,6 +58,7 @@ class NonceField extends React.Component<Props> {
|
||||
onChange={onChange}
|
||||
disabled={noncePending}
|
||||
showInvalidWithoutValue={true}
|
||||
showInvalidBeforeBlur={showInvalidBeforeBlur}
|
||||
/>
|
||||
{noncePending ? (
|
||||
<div className="Nonce-spinner">
|
||||
|
@ -106,7 +106,7 @@ class AdvancedGas extends React.Component<Props, State> {
|
||||
)}
|
||||
{nonceField && (
|
||||
<div className="AdvancedGas-nonce">
|
||||
<NonceField alwaysDisplay={true} />
|
||||
<NonceField alwaysDisplay={true} showInvalidBeforeBlur={true} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -76,6 +76,16 @@ class SimpleGas extends React.Component<Props> {
|
||||
min: gasEstimates ? gasEstimates.safeLow : gasPriceDefaults.min
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc On retrieval of gas estimates,
|
||||
* the current gas price may be lower than the lowest recommended price.
|
||||
* `rc-slider` will force the onChange if the value is too low, so we
|
||||
* ensure it at least passes the lower boundary.
|
||||
* When this occurs, the logic in `UNSAFE_componentWillReceiveProps` fires,
|
||||
* and it cannot happen again from that point forward.
|
||||
*/
|
||||
const actualGasPrice = Math.max(this.getGasPriceGwei(gasPrice.value), bounds.min);
|
||||
|
||||
return (
|
||||
<div className="SimpleGas row form-group">
|
||||
<div className="SimpleGas-title">
|
||||
@ -103,7 +113,7 @@ class SimpleGas extends React.Component<Props> {
|
||||
min={bounds.min}
|
||||
max={bounds.max}
|
||||
step={bounds.min < 1 ? 0.1 : 1}
|
||||
value={this.getGasPriceGwei(gasPrice.value)}
|
||||
value={actualGasPrice}
|
||||
tipFormatter={this.formatTooltip}
|
||||
disabled={isGasEstimating}
|
||||
/>
|
||||
|
@ -17,7 +17,7 @@ $speed: 500ms;
|
||||
@mixin decrypt-title {
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
margin: 0 0 30px;
|
||||
margin: $space 0 2rem;
|
||||
font-weight: normal;
|
||||
animation: decrypt-enter $speed ease 1;
|
||||
}
|
||||
@ -50,7 +50,7 @@ $speed: 500ms;
|
||||
&-generate {
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
margin-top: $space;
|
||||
margin: $space 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import {
|
||||
HardwareWalletName,
|
||||
SecureWalletName,
|
||||
InsecureWalletName,
|
||||
MiscWalletName,
|
||||
@ -23,6 +24,7 @@ import { transactionFieldsActions } from 'features/transaction';
|
||||
import { notificationsActions } from 'features/notifications';
|
||||
import LedgerIcon from 'assets/images/wallets/ledger.svg';
|
||||
import TrezorIcon from 'assets/images/wallets/trezor.svg';
|
||||
import SafeTIcon from 'assets/images/wallets/safe-t.svg';
|
||||
import ParitySignerIcon from 'assets/images/wallets/parity-signer.svg';
|
||||
import { Errorable } from 'components';
|
||||
import { DisabledWallets } from './disables';
|
||||
@ -34,6 +36,7 @@ import {
|
||||
PrivateKeyDecrypt,
|
||||
PrivateKeyValue,
|
||||
TrezorDecrypt,
|
||||
SafeTminiDecrypt,
|
||||
ViewOnlyDecrypt,
|
||||
Web3Decrypt,
|
||||
WalletButton,
|
||||
@ -96,12 +99,17 @@ export interface InsecureWalletInfo extends BaseWalletInfo {
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface MiscWalletInfo extends InsecureWalletInfo {}
|
||||
|
||||
type HardwareWallets = { [key in HardwareWalletName]: SecureWalletInfo };
|
||||
type SecureWallets = { [key in SecureWalletName]: SecureWalletInfo };
|
||||
type InsecureWallets = { [key in InsecureWalletName]: InsecureWalletInfo };
|
||||
type MiscWallet = { [key in MiscWalletName]: MiscWalletInfo };
|
||||
type Wallets = SecureWallets & InsecureWallets & MiscWallet;
|
||||
type Wallets = HardwareWallets & SecureWallets & InsecureWallets & MiscWallet;
|
||||
|
||||
const SECURE_WALLETS = Object.values(SecureWalletName);
|
||||
const HARDWARE_WALLETS = Object.values(HardwareWalletName);
|
||||
/** @desc Hardware wallets are secure too, but we want to avoid duplication. */
|
||||
const SECURE_WALLETS = Object.values(SecureWalletName).filter(
|
||||
value => !HARDWARE_WALLETS.includes(value)
|
||||
);
|
||||
const INSECURE_WALLETS = Object.values(InsecureWalletName);
|
||||
const MISC_WALLETS = Object.values(MiscWalletName);
|
||||
|
||||
@ -141,6 +149,16 @@ const WalletDecrypt = withRouter<Props>(
|
||||
helpLink:
|
||||
'https://support.mycrypto.com/accessing-your-wallet/how-to-use-your-trezor-with-mycrypto.html'
|
||||
},
|
||||
[SecureWalletName.SAFE_T]: {
|
||||
lid: 'X_SAFE_T',
|
||||
icon: SafeTIcon,
|
||||
description: 'ADD_HARDWAREDESC',
|
||||
component: SafeTminiDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
// TODO - Update with the right id once available
|
||||
helpLink: 'https://www.archos.com/fr/products/crypto/faq.html'
|
||||
},
|
||||
[SecureWalletName.PARITY_SIGNER]: {
|
||||
lid: 'X_PARITYSIGNER',
|
||||
icon: ParitySignerIcon,
|
||||
@ -297,7 +315,7 @@ const WalletDecrypt = withRouter<Props>(
|
||||
<h2 className="WalletDecrypt-wallets-title">{translate('DECRYPT_ACCESS')}</h2>
|
||||
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{SECURE_WALLETS.map((walletType: SecureWalletName) => {
|
||||
{HARDWARE_WALLETS.map((walletType: SecureWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
<WalletButton
|
||||
@ -316,23 +334,22 @@ const WalletDecrypt = withRouter<Props>(
|
||||
})}
|
||||
</div>
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{INSECURE_WALLETS.map((walletType: InsecureWalletName) => {
|
||||
{SECURE_WALLETS.map((walletType: SecureWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
<WalletButton
|
||||
key={walletType}
|
||||
name={translateRaw(wallet.lid)}
|
||||
example={wallet.example}
|
||||
description={translateRaw(wallet.description)}
|
||||
helpLink={wallet.helpLink}
|
||||
walletType={walletType}
|
||||
isSecure={false}
|
||||
isSecure={true}
|
||||
isDisabled={this.isWalletDisabled(walletType)}
|
||||
disableReason={reasons[walletType]}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{MISC_WALLETS.map((walletType: MiscWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
@ -343,6 +360,26 @@ const WalletDecrypt = withRouter<Props>(
|
||||
helpLink={wallet.helpLink}
|
||||
walletType={walletType}
|
||||
isReadOnly={true}
|
||||
isSecure={true}
|
||||
isDisabled={this.isWalletDisabled(walletType)}
|
||||
disableReason={reasons[walletType]}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="WalletDecrypt-wallets-row">
|
||||
{INSECURE_WALLETS.map((walletType: InsecureWalletName) => {
|
||||
const wallet = this.WALLETS[walletType];
|
||||
return (
|
||||
<WalletButton
|
||||
key={walletType}
|
||||
name={translateRaw(wallet.lid)}
|
||||
example={wallet.example}
|
||||
helpLink={wallet.helpLink}
|
||||
walletType={walletType}
|
||||
isSecure={false}
|
||||
isDisabled={this.isWalletDisabled(walletType)}
|
||||
disableReason={reasons[walletType]}
|
||||
onClick={this.handleWalletChoice}
|
||||
|
@ -17,6 +17,13 @@ interface Props {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
interface SignerAddress {
|
||||
address: string;
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
type SignerQrContent = SignerAddress | string;
|
||||
|
||||
class ParitySignerDecryptClass extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
@ -36,13 +43,13 @@ class ParitySignerDecryptClass extends PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
private unlockAddress = (address: string) => {
|
||||
if (!isValidETHAddress(address)) {
|
||||
private unlockAddress = (content: SignerQrContent) => {
|
||||
if (typeof content === 'string' || !isValidETHAddress(content.address)) {
|
||||
this.props.showNotification('danger', 'Not a valid address!');
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onUnlock(new ParitySignerWallet(address));
|
||||
this.props.onUnlock(new ParitySignerWallet(content.address));
|
||||
};
|
||||
}
|
||||
|
||||
|
31
common/components/WalletDecrypt/components/SafeT.scss
Normal file
31
common/components/WalletDecrypt/components/SafeT.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.SafeTminiDecrypt {
|
||||
text-align: center;
|
||||
|
||||
&-help {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&-error {
|
||||
opacity: 0;
|
||||
transition: none;
|
||||
|
||||
&.is-showing {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-buy {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.Spinner {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
153
common/components/WalletDecrypt/components/SafeT.tsx
Normal file
153
common/components/WalletDecrypt/components/SafeT.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { SecureWalletName, safeTReferralURL } from 'config';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { SafeTWallet } from 'libs/wallet';
|
||||
import { AppState } from 'features/reducers';
|
||||
import { getSingleDPath, getPaths } from 'features/config';
|
||||
import { Spinner, NewTabLink } from 'components/ui';
|
||||
import UnsupportedNetwork from './UnsupportedNetwork';
|
||||
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||
import './SafeT.scss';
|
||||
|
||||
//todo: conflicts with comment in walletDecrypt -> onUnlock method
|
||||
interface OwnProps {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
dPath: DPath | undefined;
|
||||
dPaths: DPath[];
|
||||
}
|
||||
|
||||
// todo: nearly duplicates ledger component props
|
||||
interface State {
|
||||
publicKey: string;
|
||||
chainCode: string;
|
||||
dPath: DPath;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class SafeTminiDecryptClass extends PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: this.props.dPath || this.props.dPaths[0],
|
||||
error: null,
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
|
||||
const showErr = error ? 'is-showing' : '';
|
||||
|
||||
if (!dPath) {
|
||||
return <UnsupportedNetwork walletType={translateRaw('X_SAFE_T')} />;
|
||||
}
|
||||
|
||||
// todo: update help link
|
||||
return (
|
||||
<div className="SafeTminiDecrypt">
|
||||
<button
|
||||
className="SafeTminiDecrypt-decrypt btn btn-primary btn-lg btn-block"
|
||||
onClick={this.handleNullConnect}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="SafeTminiDecrypt-message">
|
||||
<Spinner light={true} />
|
||||
{translate('WALLET_UNLOCKING')}
|
||||
</div>
|
||||
) : (
|
||||
translate('ADD_SAFE_T_SCAN')
|
||||
)}
|
||||
</button>
|
||||
|
||||
<NewTabLink className="SafeTminiDecrypt-buy btn btn-sm btn-default" href={safeTReferralURL}>
|
||||
{translate('ORDER_SAFE_T')}
|
||||
</NewTabLink>
|
||||
|
||||
<div className={`SafeTminiDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
|
||||
|
||||
<DeterministicWalletsModal
|
||||
isOpen={!!publicKey && !!chainCode}
|
||||
publicKey={publicKey}
|
||||
chainCode={chainCode}
|
||||
dPath={dPath}
|
||||
dPaths={this.props.dPaths}
|
||||
onCancel={this.handleCancel}
|
||||
onConfirmAddress={this.handleUnlock}
|
||||
onPathChange={this.handlePathChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handlePathChange = (dPath: DPath) => {
|
||||
this.setState({ dPath });
|
||||
this.handleConnect(dPath);
|
||||
};
|
||||
|
||||
private handleConnect = (dPath: DPath): void => {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: null
|
||||
});
|
||||
|
||||
SafeTWallet.getChainCode(dPath.value)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
dPath,
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode,
|
||||
isLoading: false
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
error: err.message,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private handleCancel = () => {
|
||||
this.reset();
|
||||
};
|
||||
|
||||
private handleUnlock = (address: string, index: number) => {
|
||||
this.props.onUnlock(new SafeTWallet(address, this.state.dPath.value, index));
|
||||
this.reset();
|
||||
};
|
||||
|
||||
private handleNullConnect = (): void => {
|
||||
this.handleConnect(this.state.dPath);
|
||||
};
|
||||
|
||||
private reset() {
|
||||
this.setState({
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: this.props.dPath || this.props.dPaths[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return {
|
||||
dPath: getSingleDPath(state, SecureWalletName.SAFE_T),
|
||||
dPaths: getPaths(state, SecureWalletName.SAFE_T)
|
||||
};
|
||||
}
|
||||
|
||||
export const SafeTminiDecrypt = connect(mapStateToProps)(SafeTminiDecryptClass);
|
@ -30,7 +30,7 @@
|
||||
animation: wallet-button-enter 400ms ease 1;
|
||||
animation-fill-mode: backwards;
|
||||
|
||||
@for $i from 0 to 5 {
|
||||
@for $i from 0 to 6 {
|
||||
&:nth-child(#{$i}) {
|
||||
animation-delay: 100ms + ($i * 60ms);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ export * from './Mnemonic';
|
||||
export * from './ParitySigner';
|
||||
export * from './PrivateKey';
|
||||
export * from './Trezor';
|
||||
export * from './SafeT';
|
||||
export * from './ViewOnly';
|
||||
export * from './WalletButton';
|
||||
export * from './Web3';
|
||||
|
@ -22,9 +22,10 @@ export const DISABLE_WALLETS: { [key in WalletMode]: DisabledWallets } = {
|
||||
}
|
||||
},
|
||||
[WalletMode.UNABLE_TO_SIGN]: {
|
||||
wallets: [SecureWalletName.TREZOR, MiscWalletName.VIEW_ONLY],
|
||||
wallets: [SecureWalletName.TREZOR, SecureWalletName.SAFE_T, MiscWalletName.VIEW_ONLY],
|
||||
reasons: {
|
||||
[SecureWalletName.TREZOR]: 'This wallet can’t sign messages',
|
||||
[SecureWalletName.SAFE_T]: 'This wallet can’t sign messages',
|
||||
[MiscWalletName.VIEW_ONLY]: 'This wallet can’t sign messages'
|
||||
}
|
||||
}
|
||||
|
@ -24,4 +24,3 @@ export { default as ParityQrSigner } from './ParityQrSigner';
|
||||
export { default as ElectronNav } from './ElectronNav';
|
||||
export { default as AddressBookTable } from './AddressBookTable';
|
||||
export { default as Errorable } from './Errorable';
|
||||
export { default as AppAlphaNotice } from './AppAlphaNotice';
|
||||
|
@ -9,7 +9,7 @@ interface Props extends ReactSelectProps {
|
||||
|
||||
export default class Dropdown extends React.Component<Props> {
|
||||
public state = {
|
||||
selectedOption: { value: '', label: '' },
|
||||
selectedOption: { value: undefined, label: '' },
|
||||
hasBlurred: false
|
||||
};
|
||||
|
||||
@ -43,13 +43,13 @@ export default class Dropdown extends React.Component<Props> {
|
||||
});
|
||||
}
|
||||
}}
|
||||
{...this.props}
|
||||
className={`${this.props.className} ${this.state.hasBlurred ? 'has-blurred' : ''}`}
|
||||
value={value}
|
||||
onChange={obj => {
|
||||
this.handleChange(obj as any);
|
||||
onChange();
|
||||
onChange(obj as any);
|
||||
}}
|
||||
{...this.props}
|
||||
onBlur={e => {
|
||||
this.setState({ hasBlurred: true });
|
||||
if (this.props && this.props.onBlur) {
|
||||
|
@ -49,7 +49,7 @@ class Input extends React.Component<Props, State> {
|
||||
} else if (!hasBlurred && !showInvalidBeforeBlur) {
|
||||
validClass = '';
|
||||
}
|
||||
if (!hasValue && showInvalidWithoutValue) {
|
||||
if ((!isStateless || showInvalidBeforeBlur) && !hasValue && showInvalidWithoutValue) {
|
||||
validClass = 'invalid';
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,8 @@ interface Props {
|
||||
modalStyle?: CSSProperties;
|
||||
hasButtons?: number;
|
||||
buttons?: IButton[];
|
||||
disableButtons?: any;
|
||||
disableButtons?: boolean;
|
||||
hideButtons?: boolean;
|
||||
handleClose(): void;
|
||||
}
|
||||
|
||||
@ -45,7 +46,7 @@ export default class ModalBody extends React.Component<Props> {
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { title, children, modalStyle, hasButtons, handleClose } = this.props;
|
||||
const { title, children, modalStyle, hasButtons, hideButtons, handleClose } = this.props;
|
||||
return (
|
||||
<div
|
||||
className="Modal"
|
||||
@ -68,9 +69,9 @@ export default class ModalBody extends React.Component<Props> {
|
||||
|
||||
<div className="Modal-content" ref={div => (this.modalContent = div as HTMLElement)}>
|
||||
{children}
|
||||
<div className={`Modal-fade ${!hasButtons ? 'has-no-footer' : ''}`} />
|
||||
<div className={`Modal-fade ${!hasButtons || hideButtons ? 'has-no-footer' : ''}`} />
|
||||
</div>
|
||||
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
|
||||
{hasButtons && !hideButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ interface Props {
|
||||
isOpen?: boolean;
|
||||
title?: React.ReactNode;
|
||||
disableButtons?: boolean;
|
||||
hideButtons?: boolean;
|
||||
children: React.ReactNode;
|
||||
buttons?: IButton[];
|
||||
maxWidth?: number;
|
||||
@ -56,7 +57,16 @@ export default class Modal extends PureComponent<Props, {}> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
|
||||
const {
|
||||
isOpen,
|
||||
title,
|
||||
children,
|
||||
buttons,
|
||||
disableButtons,
|
||||
hideButtons,
|
||||
handleClose,
|
||||
maxWidth
|
||||
} = this.props;
|
||||
const hasButtons = buttons && buttons.length;
|
||||
const modalStyle: ModalStyle = {};
|
||||
|
||||
@ -65,7 +75,16 @@ export default class Modal extends PureComponent<Props, {}> {
|
||||
modalStyle.maxWidth = `${maxWidth}px`;
|
||||
}
|
||||
|
||||
const modalBodyProps = { title, children, modalStyle, hasButtons, buttons, handleClose };
|
||||
const modalBodyProps = {
|
||||
title,
|
||||
children,
|
||||
modalStyle,
|
||||
hasButtons,
|
||||
buttons,
|
||||
disableButtons,
|
||||
hideButtons,
|
||||
handleClose
|
||||
};
|
||||
|
||||
const modal = (
|
||||
<TransitionGroup>
|
||||
|
@ -87,7 +87,7 @@ class SwapDropdown extends PureComponent<Props, State> {
|
||||
key={opt.name}
|
||||
option={opt}
|
||||
isMain={false}
|
||||
isDisabled={opt.name === disabledOption}
|
||||
isDisabled={opt.name === disabledOption || opt.status === 'unavailable'}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
))}
|
||||
@ -140,6 +140,23 @@ class SwapDropdown extends PureComponent<Props, State> {
|
||||
(opt1, opt2) => (opt1.id.toLowerCase() > opt2.id.toLowerCase() ? 1 : -1)
|
||||
);
|
||||
|
||||
// Sort unavailable options last
|
||||
otherOptions = otherOptions.sort((opt1, opt2) => {
|
||||
if (opt1.status === 'available' && opt2.status === 'unavailable') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opt1.status === 'available' && opt2.status === 'available') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (opt1.status === 'unavailable' && opt2.status === 'available') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.setState({ mainOptions, otherOptions });
|
||||
}
|
||||
}
|
||||
|
35
common/components/ui/Warning.scss
Normal file
35
common/components/ui/Warning.scss
Normal file
@ -0,0 +1,35 @@
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.Warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: 2px solid $brand-danger;
|
||||
padding: $space-sm;
|
||||
font-size: $font-size-base;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 1px 1px rgba(#000, 0.12);
|
||||
margin-bottom: $space;
|
||||
|
||||
&.highlighted {
|
||||
background-color: lighten($brand-danger, 30%);
|
||||
}
|
||||
|
||||
&-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
margin-left: $space * 0.4;
|
||||
margin-right: $space * 0.8;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
color: $brand-danger;
|
||||
}
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 $space;
|
||||
}
|
||||
}
|
22
common/components/ui/Warning.tsx
Normal file
22
common/components/ui/Warning.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import './Warning.scss';
|
||||
|
||||
interface WarningProps {
|
||||
highlighted?: boolean;
|
||||
}
|
||||
|
||||
const Warning: React.SFC<WarningProps> = ({ highlighted, children }) => {
|
||||
const className = `Warning ${highlighted ? 'highlighted' : ''}`;
|
||||
|
||||
return (
|
||||
<section className={className}>
|
||||
<section className="Warning-icon">
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
</section>
|
||||
<section className="Warning-content">{children}</section>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Warning;
|
@ -16,5 +16,6 @@ export { default as TextArea } from './TextArea';
|
||||
export { default as Address } from './Address';
|
||||
export { default as CodeBlock } from './CodeBlock';
|
||||
export { default as Toggle } from './Toggle';
|
||||
export { default as Warning } from './Warning';
|
||||
export * from './Expandable';
|
||||
export * from './InlineSpinner';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BTCTxExplorer, ETHTxExplorer } from './data';
|
||||
|
||||
export type WhitelistedCoins = 'BTC' | 'REP' | 'ETH';
|
||||
export type WhitelistedCoins = 'BTC' | 'REP' | 'ETH' | 'XMR';
|
||||
const serverURL = 'https://bity.myetherapi.com';
|
||||
const bityURL = 'https://bity.com/api';
|
||||
const BTCMin = 0.01;
|
||||
@ -11,7 +11,8 @@ const BTCMax = 3;
|
||||
// value = percent higher/lower than 0.01 BTC worth
|
||||
const buffers = {
|
||||
ETH: 0.1,
|
||||
REP: 0.2
|
||||
REP: 0.2,
|
||||
XMR: 0.3
|
||||
};
|
||||
|
||||
// rate must be BTC[KIND]
|
||||
|
8
common/config/contracts/rsk_testnet.json
Normal file
8
common/config/contracts/rsk_testnet.json
Normal file
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"name": "Bridge",
|
||||
"address": "0x0000000000000000000000000000000001000006",
|
||||
"abi":
|
||||
"[{ \"name\": \"getFederationAddress\", \"type\": \"function\", \"constant\": true, \"inputs\": [], \"outputs\": [{ \"name\": \"\", \"type\": \"string\" }] }]"
|
||||
}
|
||||
]
|
@ -9,17 +9,9 @@ export const languages = require('./languages.json');
|
||||
export const discordURL = 'https://discord.gg/VSaTXEA';
|
||||
|
||||
// Displays in the footer
|
||||
const VERSION_ELECTRON = packageJson['electron-version'];
|
||||
const VERSION_WEB = packageJson.version;
|
||||
export const VERSION = process.env.BUILD_ELECTRON ? VERSION_ELECTRON : VERSION_WEB;
|
||||
export const VERSION = packageJson.version;
|
||||
export const N_FACTOR = 8192;
|
||||
|
||||
// Bricks the app once this date has been exceeded. Remember to update these 2
|
||||
// whenever making a new app release.
|
||||
// It is currently set to: Wednesday, July 25, 2018 12:00:00 AM (GMT)
|
||||
// TODO: Remove me once app alpha / release candidates are done
|
||||
export const APP_ALPHA_EXPIRATION = 1532476800000;
|
||||
|
||||
// Displays at the top of the site, make message empty string to remove.
|
||||
// Type can be primary, warning, danger, success, info, or blank for grey.
|
||||
// Message must be a JSX element if you want to use HTML.
|
||||
@ -46,7 +38,9 @@ export const etherChainExplorerInst = makeExplorer({
|
||||
export const donationAddressMap = {
|
||||
BTC: '32oirLEzZRhi33RCXDF9WHJjEb8RsrSss3',
|
||||
ETH: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
||||
REP: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520'
|
||||
REP: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
||||
XMR:
|
||||
'4GdoN7NCTi8a5gZug7PrwZNKjvHFmKeV11L6pNJPgj5QNEHsN6eeX3DaAQFwZ1ufD4LYCZKArktt113W7QjWvQ7CW7F7tDFvS511SNfZV7'
|
||||
};
|
||||
|
||||
export const gasEstimateCacheTime = 60000;
|
||||
@ -61,6 +55,9 @@ export const MINIMUM_PASSWORD_LENGTH = 12;
|
||||
export const knowledgeBaseURL = 'https://support.mycrypto.com';
|
||||
export const ledgerReferralURL = 'https://www.ledgerwallet.com/r/1985?path=/products/';
|
||||
export const trezorReferralURL = 'https://shop.trezor.io?a=mycrypto.com';
|
||||
// TODO - Update url
|
||||
export const safeTReferralURL =
|
||||
'https://www.archos.com/fr/products/crypto/archos_safetmini/index.html';
|
||||
export const bitboxReferralURL = 'https://digitalbitbox.com/?ref=mycrypto';
|
||||
// TODO - Update url, this is MEW's
|
||||
export const bityReferralURL = 'https://bity.com/af/jshkb37v';
|
||||
@ -75,12 +72,14 @@ export enum SecureWalletName {
|
||||
WEB3 = 'web3',
|
||||
LEDGER_NANO_S = 'ledgerNanoS',
|
||||
TREZOR = 'trezor',
|
||||
SAFE_T = 'safeTmini',
|
||||
PARITY_SIGNER = 'paritySigner'
|
||||
}
|
||||
|
||||
export enum HardwareWalletName {
|
||||
LEDGER_NANO_S = 'ledgerNanoS',
|
||||
TREZOR = 'trezor'
|
||||
TREZOR = 'trezor',
|
||||
SAFE_T = 'safeTmini'
|
||||
}
|
||||
|
||||
export enum InsecureWalletName {
|
||||
|
@ -8,6 +8,11 @@ export const ETH_TREZOR: DPath = {
|
||||
value: "m/44'/60'/0'/0"
|
||||
};
|
||||
|
||||
export const ETH_SAFE_T: DPath = {
|
||||
label: 'Safe-T (ETH)',
|
||||
value: "m/44'/60'/0'/0"
|
||||
};
|
||||
|
||||
export const ETH_LEDGER: DPath = {
|
||||
label: 'Ledger (ETH)',
|
||||
value: "m/44'/60'/0'"
|
||||
@ -23,6 +28,11 @@ export const ETC_TREZOR: DPath = {
|
||||
value: "m/44'/61'/0'/0"
|
||||
};
|
||||
|
||||
export const ETC_SAFE_T: DPath = {
|
||||
label: 'Safe-T (ETC)',
|
||||
value: "m/44'/61'/0'/0"
|
||||
};
|
||||
|
||||
export const ETH_TESTNET: DPath = {
|
||||
label: 'Testnet (ETH)',
|
||||
value: "m/44'/1'/0'/0"
|
||||
@ -83,6 +93,11 @@ export const RSK_TESTNET: DPath = {
|
||||
value: "m/44'/37310'/0'/0"
|
||||
};
|
||||
|
||||
export const RSK_MAINNET: DPath = {
|
||||
label: 'Mainnet (RSK)',
|
||||
value: "m/44'/137'/0'/0"
|
||||
};
|
||||
|
||||
export const GO_DEFAULT: DPath = {
|
||||
label: 'Default (GO)',
|
||||
value: "m/44'/6060'/0'/0"
|
||||
@ -101,9 +116,11 @@ export const ESN_DEFAULT: DPath = {
|
||||
export const DPaths: DPath[] = [
|
||||
ETH_DEFAULT,
|
||||
ETH_TREZOR,
|
||||
ETH_SAFE_T,
|
||||
ETH_LEDGER,
|
||||
ETC_LEDGER,
|
||||
ETC_TREZOR,
|
||||
ETC_SAFE_T,
|
||||
ETH_TESTNET,
|
||||
EXP_DEFAULT,
|
||||
UBQ_DEFAULT,
|
||||
@ -114,6 +131,7 @@ export const DPaths: DPath[] = [
|
||||
ETSC_DEFAULT,
|
||||
EGEM_DEFAULT,
|
||||
CLO_DEFAULT,
|
||||
RSK_MAINNET,
|
||||
RSK_TESTNET,
|
||||
GO_DEFAULT,
|
||||
EOSC_DEFAULT,
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
discordURL,
|
||||
ledgerReferralURL,
|
||||
trezorReferralURL,
|
||||
safeTReferralURL,
|
||||
ethercardReferralURL,
|
||||
keepkeyReferralURL,
|
||||
steelyReferralURL
|
||||
@ -84,6 +85,10 @@ export const affiliateLinks: Link[] = [
|
||||
link: trezorReferralURL,
|
||||
text: translateRaw('TREZOR_REFERAL')
|
||||
},
|
||||
{
|
||||
link: safeTReferralURL,
|
||||
text: translateRaw('SAFE_T_REFERAL')
|
||||
},
|
||||
{
|
||||
link: keepkeyReferralURL,
|
||||
text: translateRaw('KEEPKEY_REFERRAL')
|
||||
|
@ -2310,7 +2310,7 @@
|
||||
"decimal": 8
|
||||
},
|
||||
{
|
||||
"address": "0xE94327D07Fc17907b4DB788E5aDf2ed424adDff6",
|
||||
"address": "0x1985365e9f78359a9B6AD760e32412f4a445E862",
|
||||
"symbol": "REP",
|
||||
"decimal": 18
|
||||
},
|
||||
@ -2462,7 +2462,7 @@
|
||||
{
|
||||
"address": "0x12fCd6463E66974cF7bBC24FFC4d40d6bE458283",
|
||||
"symbol": "GBX",
|
||||
"decimal": 18
|
||||
"decimal": 8
|
||||
},
|
||||
{
|
||||
"address": "0x7728dFEF5aBd468669EB7f9b48A7f70a501eD29D",
|
||||
@ -3328,5 +3328,32 @@
|
||||
"address": "0xAc709FcB44a43c35F0DA4e3163b117A17F3770f5",
|
||||
"symbol": "ARC",
|
||||
"decimal": 18
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"address": "0xDF2C7238198Ad8B389666574f2d8bc411A4b7428",
|
||||
"symbol": "MFT",
|
||||
"decimal": 18
|
||||
},
|
||||
{
|
||||
"address": "0xc92d6e3e64302c59d734f3292e2a13a13d7e1817",
|
||||
"symbol": "FXC",
|
||||
"decimal": 8
|
||||
},
|
||||
{
|
||||
"address": "0x4f3afec4e5a3f2a6a1a411def7d7dfe50ee057bf",
|
||||
"symbol": "DGX 2.0",
|
||||
"decimal": 9
|
||||
},
|
||||
{
|
||||
"address": "0xd9a12cde03a86e800496469858de8581d3a5353d",
|
||||
"symbol": "YUP",
|
||||
"decimal": 18
|
||||
},
|
||||
{
|
||||
"address": "0x6aEDbF8dFF31437220dF351950Ba2a3362168d1b",
|
||||
"symbol": "DGS",
|
||||
"decimal": 8
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -1 +1 @@
|
||||
[]
|
||||
[]
|
1
common/config/tokens/rsk_testnet.json
Normal file
1
common/config/tokens/rsk_testnet.json
Normal file
@ -0,0 +1 @@
|
||||
[]
|
@ -1,27 +0,0 @@
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.WelcomeSlide {
|
||||
&-alert {
|
||||
display: flex;
|
||||
border-top: 2px solid color(brand-danger);
|
||||
padding: $space-sm;
|
||||
font-size: $font-size-base;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 1px 1px rgba(#000, 0.12);
|
||||
margin-bottom: $space;
|
||||
|
||||
&-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
margin-left: $space * 0.4;
|
||||
margin-right: $space * 0.8;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
color: color(brand-danger);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,31 +2,20 @@ import React from 'react';
|
||||
|
||||
import translate from 'translations';
|
||||
import onboardIconOne from 'assets/images/onboarding/slide-01.svg';
|
||||
import { Warning } from 'components/ui';
|
||||
import OnboardSlide from './OnboardSlide';
|
||||
|
||||
import './WelcomeSlide.scss';
|
||||
|
||||
const WelcomeSlide = () => {
|
||||
const header = translate('ONBOARD_WELCOME_TITLE');
|
||||
const subheader = <small>{translate('ONBOARD_WELCOME_CONTENT__3')}</small>;
|
||||
|
||||
const content = (
|
||||
<div>
|
||||
<div className="WelcomeSlide-alert">
|
||||
<div className="WelcomeSlide-alert-icon">
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
</div>
|
||||
<span>
|
||||
{translate('ONBOARD_WELCOME_CONTENT__1')}
|
||||
{translate('ONBOARD_WELCOME_CONTENT__2')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="WelcomeSlide-alert">
|
||||
<div className="WelcomeSlide-alert-icon">
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
</div>
|
||||
{translate('ONBOARD_WELCOME_CONTENT__8')}
|
||||
</div>
|
||||
<Warning>
|
||||
{translate('ONBOARD_WELCOME_CONTENT__1')}
|
||||
{translate('ONBOARD_WELCOME_CONTENT__2')}
|
||||
</Warning>
|
||||
<Warning>{translate('ONBOARD_WELCOME_CONTENT__8')}</Warning>
|
||||
<h5>{translate('ONBOARD_WELCOME_CONTENT__4')}</h5>
|
||||
<ul>
|
||||
<li>{translate('ONBOARD_WELCOME_CONTENT__5')}</li>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { AppState } from 'features/reducers';
|
||||
import { paritySignerActions } from 'features/paritySigner';
|
||||
@ -95,7 +95,7 @@ class QrSignerModal extends React.Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.finalizeSignature(signature);
|
||||
this.props.finalizeSignature(addHexPrefix(signature));
|
||||
this.setState({ scan: false });
|
||||
};
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { AppState } from 'features/reducers';
|
||||
import { getOffline } from 'features/config';
|
||||
import { ElectronNav, AppAlphaNotice } from 'components';
|
||||
import { ElectronNav } from 'components';
|
||||
import OfflineTab from './OfflineTab';
|
||||
import Notifications from './Notifications';
|
||||
import './ElectronTemplate.scss';
|
||||
@ -33,7 +33,6 @@ class ElectronTemplate extends Component<Props, {}> {
|
||||
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
|
||||
</div>
|
||||
<Notifications />
|
||||
<AppAlphaNotice />
|
||||
</div>
|
||||
<div className="ElectronTemplate-draggable" />
|
||||
</div>
|
||||
|
@ -195,6 +195,12 @@ class InteractForm extends Component<Props, State> {
|
||||
abiJson: fullContract.abi || '',
|
||||
contract
|
||||
});
|
||||
} else {
|
||||
this.props.setCurrentTo('');
|
||||
this.setState({
|
||||
abiJson: '',
|
||||
contract
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import translate from 'translations';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { AppState } from 'features/reducers';
|
||||
import * as selectors from 'features/selectors';
|
||||
import { getOffline, getNetworkConfig } from 'features/config';
|
||||
import { scheduleSelectors } from 'features/schedule';
|
||||
import { notificationsActions } from 'features/notifications';
|
||||
import {
|
||||
AddressField,
|
||||
AmountField,
|
||||
@ -38,7 +39,26 @@ interface StateProps {
|
||||
useScheduling: scheduleSelectors.ICurrentSchedulingToggle['value'];
|
||||
}
|
||||
|
||||
class FieldsClass extends Component<StateProps> {
|
||||
interface DispatchProps {
|
||||
showNotification: notificationsActions.TShowNotification;
|
||||
}
|
||||
|
||||
class FieldsClass extends Component<StateProps & DispatchProps> {
|
||||
public componentDidCatch(error: Error) {
|
||||
if (error.message === 'Serialized transaction not found') {
|
||||
/**
|
||||
* @desc Occasionally, when a new signed transaction matches a previous transaction,
|
||||
* the nonce does not update, since the transaction has not yet been confirmed. This triggers
|
||||
* the <Amounts /> component inside the <ConfirmationModal /> of <TXMetaDataPanel /> to throw
|
||||
* an error when selecting the current transaction's serialized parameters.
|
||||
* A longer term fix will involve finding a better way to calculate nonces to avoid
|
||||
* nonce duplication on serial transactions.
|
||||
*/
|
||||
this.props.showNotification('danger', translateRaw('SIMILAR_TRANSACTION_ERROR'));
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { shouldDisplay, schedulingAvailable, useScheduling } = this.props;
|
||||
|
||||
@ -54,7 +74,11 @@ class FieldsClass extends Component<StateProps> {
|
||||
<div
|
||||
className={schedulingAvailable ? 'col-sm-9 col-md-10' : 'col-sm-12 col-md-12'}
|
||||
>
|
||||
<AmountField hasUnitDropdown={true} hasSendEverything={true} />
|
||||
<AmountField
|
||||
hasUnitDropdown={true}
|
||||
hasSendEverything={true}
|
||||
showInvalidWithoutValue={true}
|
||||
/>
|
||||
</div>
|
||||
{schedulingAvailable && (
|
||||
<div className="col-sm-3 col-md-2">
|
||||
@ -102,10 +126,15 @@ class FieldsClass extends Component<StateProps> {
|
||||
}
|
||||
}
|
||||
|
||||
export const Fields = connect((state: AppState) => ({
|
||||
schedulingAvailable:
|
||||
getNetworkConfig(state).name === 'Kovan' && selectors.getUnit(state) === 'ETH',
|
||||
shouldDisplay: !selectors.isAnyOfflineWithWeb3(state),
|
||||
offline: getOffline(state),
|
||||
useScheduling: scheduleSelectors.getCurrentSchedulingToggle(state).value
|
||||
}))(FieldsClass);
|
||||
export const Fields = connect(
|
||||
(state: AppState) => ({
|
||||
schedulingAvailable:
|
||||
getNetworkConfig(state).name === 'Kovan' && selectors.getUnit(state) === 'ETH',
|
||||
shouldDisplay: !selectors.isAnyOfflineWithWeb3(state),
|
||||
offline: getOffline(state),
|
||||
useScheduling: scheduleSelectors.getCurrentSchedulingToggle(state).value
|
||||
}),
|
||||
{
|
||||
showNotification: notificationsActions.showNotification
|
||||
}
|
||||
)(FieldsClass);
|
||||
|
@ -102,6 +102,7 @@ class RequestPayment extends React.Component<Props, {}> {
|
||||
hasUnitDropdown={true}
|
||||
showAllTokens={true}
|
||||
customValidator={isValidAmount(decimal)}
|
||||
showInvalidWithoutValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ interface Props {
|
||||
signMessageRequested: messageActions.TSignMessageRequested;
|
||||
signedMessage: ISignedMessage | null;
|
||||
resetWallet: walletActions.TResetWallet;
|
||||
resetMessage: messageActions.TResetMessage;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -35,6 +36,7 @@ export class SignMessage extends Component<Props, State> {
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.props.resetWallet();
|
||||
this.props.resetMessage();
|
||||
}
|
||||
|
||||
public render() {
|
||||
@ -97,6 +99,8 @@ export class SignMessage extends Component<Props, State> {
|
||||
|
||||
private changeWallet = () => {
|
||||
this.props.resetWallet();
|
||||
this.props.resetMessage();
|
||||
this.setState(initialState);
|
||||
};
|
||||
}
|
||||
|
||||
@ -107,5 +111,6 @@ const mapStateToProps = (state: AppState) => ({
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
signMessageRequested: messageActions.signMessageRequested,
|
||||
resetWallet: walletActions.resetWallet
|
||||
resetWallet: walletActions.resetWallet,
|
||||
resetMessage: messageActions.resetMessage
|
||||
})(SignMessage);
|
||||
|
@ -40,12 +40,13 @@
|
||||
}
|
||||
|
||||
&-input {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
width: 16%;
|
||||
min-width: 3.5rem;
|
||||
height: 2rem;
|
||||
padding: 1rem;
|
||||
margin: .5rem auto;
|
||||
margin: 0.5rem auto;
|
||||
font-size: 1rem;
|
||||
margin-right: $space;
|
||||
text-align: right;
|
||||
|
@ -27,6 +27,8 @@ interface ReduxStateProps {
|
||||
bityOrderStatus: string | null;
|
||||
shapeshiftOrderStatus: string | null;
|
||||
outputTx: any;
|
||||
paymentId: string | null;
|
||||
xmrPaymentAddress: string | null;
|
||||
}
|
||||
|
||||
interface ReduxActionProps {
|
||||
@ -68,6 +70,8 @@ export default class PartThree extends PureComponent<ReduxActionProps & ReduxSta
|
||||
shapeshiftOrderStatus,
|
||||
destinationAddress,
|
||||
outputTx,
|
||||
paymentId,
|
||||
xmrPaymentAddress,
|
||||
// ACTIONS
|
||||
showNotificationWithComponent
|
||||
} = this.props;
|
||||
@ -85,7 +89,9 @@ export default class PartThree extends PureComponent<ReduxActionProps & ReduxSta
|
||||
|
||||
const PaymentInfoProps = {
|
||||
origin,
|
||||
paymentAddress
|
||||
paymentAddress,
|
||||
paymentId,
|
||||
xmrPaymentAddress
|
||||
};
|
||||
|
||||
const BitcoinQRProps = {
|
||||
|
@ -1,18 +1,31 @@
|
||||
@import "common/sass/variables";
|
||||
@import "common/sass/mixins";
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.SwapPayment {
|
||||
text-align: center;
|
||||
margin-bottom: $space;
|
||||
> * {
|
||||
margin: $space auto 0;
|
||||
width: 100%;
|
||||
max-width: 620px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
|
||||
& > input {
|
||||
margin-top: $space;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
&-address {
|
||||
@include mono;
|
||||
|
||||
display: block;
|
||||
margin: $space auto 0;
|
||||
max-width: 620px;
|
||||
width: 100%;
|
||||
font-size: $font-size-medium;
|
||||
text-align: center;
|
||||
@include mono;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $screen-sm) {
|
||||
@ -20,4 +33,16 @@
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
&-payment-id {
|
||||
h2 {
|
||||
font-weight: bolder;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-link {
|
||||
font-size: $font-size-small;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,47 @@ import React, { PureComponent } from 'react';
|
||||
|
||||
import translate from 'translations';
|
||||
import { SwapInput } from 'features/swap/types';
|
||||
import { Input } from 'components/ui';
|
||||
import { Input, Warning } from 'components/ui';
|
||||
import './PaymentInfo.scss';
|
||||
|
||||
export interface Props {
|
||||
origin: SwapInput;
|
||||
paymentAddress: string | null;
|
||||
/**
|
||||
* @desc
|
||||
* For XMR swaps, the "deposit" property in the response
|
||||
* actually refers to the "paymentId", not the payment address.
|
||||
*/
|
||||
paymentId: string | null;
|
||||
/**
|
||||
* @desc
|
||||
* For XMR swap, the actual payment address is the "sAddress"
|
||||
* property in the response.
|
||||
*/
|
||||
xmrPaymentAddress: string | null;
|
||||
}
|
||||
|
||||
export default class PaymentInfo extends PureComponent<Props, {}> {
|
||||
public render() {
|
||||
const { origin } = this.props;
|
||||
const { origin, paymentAddress, paymentId, xmrPaymentAddress } = this.props;
|
||||
const isXMRSwap = origin.label === 'XMR';
|
||||
const actualPaymentAddress = isXMRSwap ? xmrPaymentAddress : paymentAddress;
|
||||
|
||||
return (
|
||||
<section className="SwapPayment">
|
||||
{isXMRSwap && (
|
||||
<section className="SwapPayment-payment-id">
|
||||
<h2>
|
||||
{translate('USING_PAYMENT_ID')}
|
||||
<Input
|
||||
className="SwapPayment-address"
|
||||
isValid={!!paymentId}
|
||||
value={paymentId || undefined}
|
||||
disabled={true}
|
||||
/>
|
||||
</h2>
|
||||
</section>
|
||||
)}
|
||||
<h2>
|
||||
{translate('SWAP_SEND_TO', {
|
||||
$origin_amount: origin.amount.toString(),
|
||||
@ -22,11 +50,16 @@ export default class PaymentInfo extends PureComponent<Props, {}> {
|
||||
})}
|
||||
<Input
|
||||
className="SwapPayment-address"
|
||||
isValid={!!this.props.paymentAddress}
|
||||
value={this.props.paymentAddress || undefined}
|
||||
isValid={!!actualPaymentAddress}
|
||||
value={actualPaymentAddress || undefined}
|
||||
disabled={true}
|
||||
/>
|
||||
</h2>
|
||||
{isXMRSwap && (
|
||||
<Warning highlighted={true}>
|
||||
<h4>{translate('PAYMENT_ID_WARNING')}</h4>
|
||||
</Warning>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { donationAddressMap } from 'config';
|
||||
import { donationAddressMap, WhitelistedCoins } from 'config';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
|
||||
import { isValidBTCAddress, isValidETHAddress, isValidXMRAddress } from 'libs/validators';
|
||||
import { combineAndUpper } from 'utils/formatters';
|
||||
import { SwapInput } from 'features/swap/types';
|
||||
import {
|
||||
@ -18,7 +18,7 @@ import './ReceivingAddress.scss';
|
||||
|
||||
export interface StateProps {
|
||||
origin: SwapInput;
|
||||
destinationId: keyof typeof donationAddressMap;
|
||||
destinationId: WhitelistedCoins;
|
||||
isPostingOrder: boolean;
|
||||
destinationAddress: string;
|
||||
destinationKind: number;
|
||||
@ -62,13 +62,22 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
|
||||
|
||||
public render() {
|
||||
const { destinationId, destinationAddress, isPostingOrder } = this.props;
|
||||
let validAddress;
|
||||
// TODO - find better pattern here once currencies move beyond BTC, ETH, REP
|
||||
if (destinationId === 'BTC') {
|
||||
validAddress = isValidBTCAddress(destinationAddress);
|
||||
} else {
|
||||
validAddress = isValidETHAddress(destinationAddress);
|
||||
}
|
||||
|
||||
const addressValidators: { [coinOrToken: string]: (address: string) => boolean } = {
|
||||
BTC: isValidBTCAddress,
|
||||
XMR: isValidXMRAddress,
|
||||
ETH: isValidETHAddress
|
||||
};
|
||||
// If there is no matching validator for the ID, assume it's a token and use ETH.
|
||||
const addressValidator = addressValidators[destinationId] || addressValidators.ETH;
|
||||
const validAddress = addressValidator(destinationAddress);
|
||||
|
||||
const placeholders: { [coinOrToken: string]: string } = {
|
||||
BTC: donationAddressMap.BTC,
|
||||
XMR: donationAddressMap.XMR,
|
||||
ETH: donationAddressMap.ETH
|
||||
};
|
||||
const placeholder = placeholders[destinationId] || donationAddressMap.ETH;
|
||||
|
||||
return (
|
||||
<section className="SwapAddress block">
|
||||
@ -85,11 +94,7 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
|
||||
type="text"
|
||||
value={destinationAddress}
|
||||
onChange={this.onChangeDestinationAddress}
|
||||
placeholder={
|
||||
destinationId === 'BTC'
|
||||
? donationAddressMap[destinationId]
|
||||
: donationAddressMap.ETH
|
||||
}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -34,6 +34,8 @@ interface ReduxStateProps {
|
||||
bityOrderStatus: string | null;
|
||||
shapeshiftOrderStatus: string | null;
|
||||
paymentAddress: string | null;
|
||||
paymentId: string | null;
|
||||
xmrPaymentAddress: string | null;
|
||||
isOffline: boolean;
|
||||
}
|
||||
|
||||
@ -77,6 +79,8 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps & RouteComponent
|
||||
shapeshiftOrderStatus,
|
||||
isPostingOrder,
|
||||
outputTx,
|
||||
paymentId,
|
||||
xmrPaymentAddress,
|
||||
// ACTIONS
|
||||
initSwap,
|
||||
restartSwap,
|
||||
@ -159,7 +163,9 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps & RouteComponent
|
||||
stopPollShapeshiftOrderStatus,
|
||||
showNotificationWithComponent,
|
||||
destinationAddress,
|
||||
outputTx
|
||||
outputTx,
|
||||
paymentId,
|
||||
xmrPaymentAddress
|
||||
};
|
||||
|
||||
const SupportProps = {
|
||||
@ -220,6 +226,8 @@ function mapStateToProps(state: AppState) {
|
||||
bityOrderStatus: state.swap.bityOrderStatus,
|
||||
shapeshiftOrderStatus: state.swap.shapeshiftOrderStatus,
|
||||
paymentAddress: state.swap.paymentAddress,
|
||||
paymentId: state.swap.paymentId,
|
||||
xmrPaymentAddress: state.swap.xmrPaymentAddress,
|
||||
isOffline: getOffline(state)
|
||||
};
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`handleChangeNodeRequested* should select getCustomNodeConfig and match race snapshot 1`] = `
|
||||
exports[
|
||||
`handleChangeNodeRequested* should select getCustomNodeConfig and match race snapshot 1`
|
||||
] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"SELECT": Object {
|
||||
|
@ -9,10 +9,12 @@ import {
|
||||
ELLA_DEFAULT,
|
||||
ETC_LEDGER,
|
||||
ETC_TREZOR,
|
||||
ETC_SAFE_T,
|
||||
ETH_DEFAULT,
|
||||
ETH_LEDGER,
|
||||
ETH_TESTNET,
|
||||
ETH_TREZOR,
|
||||
ETH_SAFE_T,
|
||||
EXP_DEFAULT,
|
||||
POA_DEFAULT,
|
||||
TOMO_DEFAULT,
|
||||
@ -21,6 +23,7 @@ import {
|
||||
ETSC_DEFAULT,
|
||||
EGEM_DEFAULT,
|
||||
CLO_DEFAULT,
|
||||
RSK_MAINNET,
|
||||
RSK_TESTNET,
|
||||
GO_DEFAULT,
|
||||
EOSC_DEFAULT,
|
||||
@ -28,6 +31,7 @@ import {
|
||||
} from 'config/dpaths';
|
||||
import { makeExplorer } from 'utils/helpers';
|
||||
import { StaticNetworksState } from './types';
|
||||
import { TAB } from 'components/Header/components/constants';
|
||||
|
||||
const testnetDefaultGasPrice = {
|
||||
min: 0.1,
|
||||
@ -55,6 +59,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: require('config/contracts/eth.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TREZOR,
|
||||
[SecureWalletName.SAFE_T]: ETH_SAFE_T,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_DEFAULT
|
||||
},
|
||||
@ -77,6 +82,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.SAFE_T]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
},
|
||||
@ -98,6 +104,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.SAFE_T]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
},
|
||||
@ -119,6 +126,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.SAFE_T]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
},
|
||||
@ -140,6 +148,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: require('config/contracts/etc.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETC_TREZOR,
|
||||
[SecureWalletName.SAFE_T]: ETC_SAFE_T,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETC_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETC_TREZOR
|
||||
},
|
||||
@ -164,6 +173,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: require('config/contracts/ubq.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: UBQ_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: UBQ_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: UBQ_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: UBQ_DEFAULT
|
||||
},
|
||||
@ -188,6 +198,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: require('config/contracts/exp.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: EXP_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: EXP_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: EXP_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: EXP_DEFAULT
|
||||
},
|
||||
@ -205,7 +216,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
isCustom: false,
|
||||
color: '#6d2eae',
|
||||
blockExplorer: makeExplorer({
|
||||
name: 'Etherchain Light',
|
||||
name: 'POA Explorer',
|
||||
origin: 'https://poaexplorer.com',
|
||||
addressPath: 'address/search',
|
||||
blockPath: 'blocks/block'
|
||||
@ -214,6 +225,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: POA_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: POA_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: POA_DEFAULT
|
||||
},
|
||||
@ -239,6 +251,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
dPathFormats: {
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[SecureWalletName.TREZOR]: ETH_TREZOR,
|
||||
[SecureWalletName.SAFE_T]: ETH_SAFE_T,
|
||||
[SecureWalletName.LEDGER_NANO_S]: TOMO_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: TOMO_DEFAULT
|
||||
},
|
||||
@ -263,6 +276,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ELLA_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: ELLA_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ELLA_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -288,6 +302,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: MUSIC_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: MUSIC_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: MUSIC_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -312,6 +327,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETSC_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: ETSC_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETSC_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -336,6 +352,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: EGEM_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: EGEM_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: EGEM_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -360,6 +377,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: CLO_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: CLO_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: CLO_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -369,6 +387,33 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
}
|
||||
},
|
||||
|
||||
RSK: {
|
||||
id: 'RSK',
|
||||
name: 'RSK',
|
||||
unit: 'SBTC',
|
||||
chainId: 30,
|
||||
color: '#58A052',
|
||||
isCustom: false,
|
||||
blockExplorer: makeExplorer({
|
||||
name: 'RSK Explorer',
|
||||
origin: 'https://explorer.rsk.co'
|
||||
}),
|
||||
tokens: require('config/tokens/rsk.json'),
|
||||
contracts: require('config/contracts/rsk.json'),
|
||||
isTestnet: false,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: RSK_MAINNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: RSK_MAINNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: RSK_MAINNET
|
||||
},
|
||||
gasPriceSettings: {
|
||||
min: 0.183,
|
||||
max: 1.5,
|
||||
initial: 0.183
|
||||
},
|
||||
unsupportedTabs: [TAB.ENS]
|
||||
},
|
||||
|
||||
RSK_TESTNET: {
|
||||
id: 'RSK_TESTNET',
|
||||
name: 'RSK',
|
||||
@ -380,11 +425,12 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
name: 'RSK Testnet Explorer',
|
||||
origin: 'https://explorer.testnet.rsk.co'
|
||||
}),
|
||||
tokens: require('config/tokens/rsk.json'),
|
||||
contracts: require('config/contracts/rsk.json'),
|
||||
tokens: require('config/tokens/rsk_testnet.json'),
|
||||
contracts: require('config/contracts/rsk_testnet.json'),
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: RSK_TESTNET,
|
||||
[SecureWalletName.SAFE_T]: RSK_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: RSK_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: RSK_TESTNET
|
||||
},
|
||||
@ -392,7 +438,8 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
min: 0.183,
|
||||
max: 1.5,
|
||||
initial: 0.183
|
||||
}
|
||||
},
|
||||
unsupportedTabs: [TAB.ENS]
|
||||
},
|
||||
|
||||
GO: {
|
||||
@ -410,6 +457,33 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: GO_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: GO_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: GO_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
min: 2,
|
||||
max: 60,
|
||||
initial: 2
|
||||
}
|
||||
},
|
||||
|
||||
GO_TESTNET: {
|
||||
id: 'GO_TESTNET',
|
||||
name: 'GO',
|
||||
unit: 'GO',
|
||||
chainId: 31337,
|
||||
isCustom: false,
|
||||
color: '#00b04a',
|
||||
blockExplorer: makeExplorer({
|
||||
name: 'GoChain Testnet Explorer',
|
||||
origin: 'https://testnet-explorer.gochain.io'
|
||||
}),
|
||||
tokens: [],
|
||||
contracts: [],
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: GO_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: GO_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: GO_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -434,6 +508,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: [],
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: EOSC_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: EOSC_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: EOSC_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
@ -442,6 +517,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
initial: 20
|
||||
}
|
||||
},
|
||||
|
||||
ESN: {
|
||||
id: 'ESN',
|
||||
name: 'EthersocialNetwork',
|
||||
@ -457,6 +533,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = {
|
||||
contracts: require('config/contracts/esn.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ESN_DEFAULT,
|
||||
[SecureWalletName.SAFE_T]: ESN_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ESN_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
|
@ -158,6 +158,7 @@ export function isWalletFormatSupportedOnNetwork(state: AppState, format: Wallet
|
||||
const CHECK_FORMATS: DPathFormat[] = [
|
||||
SecureWalletName.LEDGER_NANO_S,
|
||||
SecureWalletName.TREZOR,
|
||||
SecureWalletName.SAFE_T,
|
||||
InsecureWalletName.MNEMONIC_PHRASE
|
||||
];
|
||||
|
||||
|
@ -27,5 +27,6 @@ export type ConfigAction = CustomNetworkAction | CustomNodeAction | NodeAction |
|
||||
|
||||
export type DPathFormat =
|
||||
| SecureWalletName.TREZOR
|
||||
| SecureWalletName.SAFE_T
|
||||
| SecureWalletName.LEDGER_NANO_S
|
||||
| InsecureWalletName.MNEMONIC_PHRASE;
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getDeterministicWallets* starting from publicKey & chainCode should match put snapshot 1`] = `
|
||||
exports[
|
||||
`getDeterministicWallets* starting from publicKey & chainCode should match put snapshot 1`
|
||||
] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
@ -215,4 +217,4 @@ Object {
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
108
common/features/handleMetaMaskPolling.spec.ts
Normal file
108
common/features/handleMetaMaskPolling.spec.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import noop from 'lodash/noop';
|
||||
|
||||
import handleMetaMaskPolling, { getActualChainId } from './handleMetaMaskPolling';
|
||||
import * as configNetworksSelectors from './config/networks/selectors';
|
||||
import { walletSelectors } from './wallet';
|
||||
|
||||
jest.mock('./config/networks/selectors');
|
||||
jest.mock('./wallet');
|
||||
|
||||
describe('getActualChainId', () => {
|
||||
it('should reject with an error if web3 does not exist', async done => {
|
||||
try {
|
||||
await getActualChainId();
|
||||
} catch (e) {
|
||||
expect(e).toBe('Web3 not found.');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('should reject with an error web3 if fails its network check', async done => {
|
||||
(global as any).web3 = {
|
||||
version: {
|
||||
getNetwork: jest.fn(callback => callback('Network check failed'))
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await getActualChainId();
|
||||
} catch (e) {
|
||||
expect(e).toBe('Network check failed');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('should return a chainId when everything is fine', async done => {
|
||||
(global as any).web3 = {
|
||||
version: {
|
||||
getNetwork: jest.fn(callback => callback(null, '1'))
|
||||
}
|
||||
};
|
||||
|
||||
const network = await getActualChainId();
|
||||
|
||||
expect(network).toBe('1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleMetaMaskPolling', () => {
|
||||
it('should do nothing when there is no wallet instance', async done => {
|
||||
(global as any).web3 = {
|
||||
version: {
|
||||
getNetwork: jest.fn(callback => callback(null, '1'))
|
||||
}
|
||||
};
|
||||
(walletSelectors as any).getWalletInst.mockReturnValue(null);
|
||||
(configNetworksSelectors as any).getNetworkByChainId.mockReturnValue('ETH');
|
||||
|
||||
const store = {
|
||||
getState: noop,
|
||||
dispatch: noop
|
||||
};
|
||||
const result = await handleMetaMaskPolling(store as any);
|
||||
|
||||
expect(result).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reload the page if the network has changed', async done => {
|
||||
(global as any).web3 = {
|
||||
version: {
|
||||
getNetwork: jest.fn(callback => callback(null, '1'))
|
||||
}
|
||||
};
|
||||
(walletSelectors as any).getWalletInst.mockReturnValue({
|
||||
network: 'ETC'
|
||||
});
|
||||
(configNetworksSelectors as any).getNetworkByChainId.mockReturnValue('ETH');
|
||||
|
||||
const store = {
|
||||
getState: noop
|
||||
};
|
||||
const result = await handleMetaMaskPolling(store as any);
|
||||
|
||||
expect(result).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reload the page if `getActualChainId` rejects', async done => {
|
||||
(global as any).web3 = {
|
||||
version: {
|
||||
getNetwork: jest.fn(callback => callback('Network check failed'))
|
||||
}
|
||||
};
|
||||
(walletSelectors as any).getWalletInst.mockReturnValue({
|
||||
network: 'ETH'
|
||||
});
|
||||
(configNetworksSelectors as any).getNetworkByChainId.mockReturnValue('ETH');
|
||||
|
||||
const store = {
|
||||
getState: noop
|
||||
};
|
||||
const result = await handleMetaMaskPolling(store as any);
|
||||
|
||||
expect(result).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
57
common/features/handleMetaMaskPolling.ts
Normal file
57
common/features/handleMetaMaskPolling.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Store } from 'redux';
|
||||
|
||||
import { Web3Wallet } from 'libs/wallet';
|
||||
import { AppState } from './reducers';
|
||||
import * as configNetworksSelectors from './config/networks/selectors';
|
||||
import { walletSelectors } from './wallet';
|
||||
|
||||
export const METAMASK_POLLING_INTERVAL: number = 1000;
|
||||
|
||||
export const getActualChainId = (): Promise<string> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const { web3 } = window as any;
|
||||
|
||||
if (!web3) {
|
||||
reject('Web3 not found.');
|
||||
}
|
||||
|
||||
return web3.version.getNetwork(
|
||||
(err: Error, network: string) => (err ? reject(err) : resolve(network))
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* MetaMask no longer refreshes the page automatically on network change,
|
||||
* so we must poll to ensure the network is the same as the locally stored version.
|
||||
* @see https://medium.com/metamask/breaking-change-no-longer-reloading-pages-on-network-change-4a3e1fd2f5e7
|
||||
*/
|
||||
export default async function handleMetaMaskPolling(store: Store<AppState>): Promise<boolean> {
|
||||
const state = store.getState();
|
||||
|
||||
try {
|
||||
// Locally stored network.
|
||||
const web3Wallet = walletSelectors.getWalletInst(state);
|
||||
|
||||
// MetaMask's actual network.
|
||||
const actualChainId = await getActualChainId();
|
||||
const actualNetwork = configNetworksSelectors.getNetworkByChainId(state, actualChainId);
|
||||
|
||||
if (
|
||||
web3Wallet &&
|
||||
(web3Wallet as Web3Wallet).network &&
|
||||
actualNetwork &&
|
||||
(web3Wallet as Web3Wallet).network !== actualNetwork.id
|
||||
) {
|
||||
window.location.reload();
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
window.location.reload();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -25,3 +25,10 @@ export function signMessageFailed(): types.SignMessageFailedAction {
|
||||
type: types.MessageActions.SIGN_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
export type TResetMessage = typeof resetMessage;
|
||||
export function resetMessage(): types.ResetMessageAction {
|
||||
return {
|
||||
type: types.MessageActions.RESET
|
||||
};
|
||||
}
|
||||
|
@ -21,6 +21,12 @@ function signMessageFailed(state: types.MessageState): types.MessageState {
|
||||
};
|
||||
}
|
||||
|
||||
function resetMessage(): types.MessageState {
|
||||
return {
|
||||
...INITIAL_STATE
|
||||
};
|
||||
}
|
||||
|
||||
export function messageReducer(
|
||||
state: types.MessageState = INITIAL_STATE,
|
||||
action: types.MessageAction
|
||||
@ -30,6 +36,8 @@ export function messageReducer(
|
||||
return signLocalMessageSucceeded(state, action);
|
||||
case types.MessageActions.SIGN_FAILED:
|
||||
return signMessageFailed(state);
|
||||
case types.MessageActions.RESET:
|
||||
return resetMessage();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import { ISignedMessage } from 'libs/signing';
|
||||
export enum MessageActions {
|
||||
SIGN_REQUESTED = 'MESSAGE_SIGN_REQUESTED',
|
||||
SIGN_LOCAL_SUCCEEDED = 'MESSAGE_SIGN_LOCAL_SUCCEEDED',
|
||||
SIGN_FAILED = 'MESSAGE_SIGN_FAILED'
|
||||
SIGN_FAILED = 'MESSAGE_SIGN_FAILED',
|
||||
RESET = 'MESSAGE_RESET'
|
||||
}
|
||||
|
||||
export interface MessageState {
|
||||
@ -24,7 +25,12 @@ export interface SignMessageFailedAction {
|
||||
type: MessageActions.SIGN_FAILED;
|
||||
}
|
||||
|
||||
export interface ResetMessageAction {
|
||||
type: MessageActions.RESET;
|
||||
}
|
||||
|
||||
export type MessageAction =
|
||||
| SignMessageRequestedAction
|
||||
| SignLocalMessageSucceededAction
|
||||
| SignMessageFailedAction;
|
||||
| SignMessageFailedAction
|
||||
| ResetMessageAction;
|
||||
|
@ -69,7 +69,7 @@ export function getDisabledWallets(state: AppState): any {
|
||||
// Some wallets are unavailable offline
|
||||
if (isOffline) {
|
||||
addReason(
|
||||
[SecureWalletName.WEB3, SecureWalletName.TREZOR],
|
||||
[SecureWalletName.WEB3, SecureWalletName.TREZOR, SecureWalletName.SAFE_T],
|
||||
'This wallet cannot be accessed offline'
|
||||
);
|
||||
}
|
||||
@ -77,6 +77,10 @@ export function getDisabledWallets(state: AppState): any {
|
||||
// Some wallets are disabled on certain platforms
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyCrypto app');
|
||||
addReason(
|
||||
[SecureWalletName.SAFE_T],
|
||||
'Coming soon. Please use the MyCrypto.com website in the meantime'
|
||||
);
|
||||
}
|
||||
|
||||
// Dedupe and sort for consistency
|
||||
|
@ -3,6 +3,7 @@ import { bindActionCreators } from 'redux';
|
||||
import { shepherdProvider, getShepherdPending, getShepherdOffline } from 'libs/nodes';
|
||||
import { setOffline, setOnline, getOffline } from 'features/config';
|
||||
import { notificationsActions } from 'features/notifications';
|
||||
import handleMetaMaskPolling, { METAMASK_POLLING_INTERVAL } from './handleMetaMaskPolling';
|
||||
import configureStore from './configureStore';
|
||||
|
||||
const store = configureStore();
|
||||
@ -90,4 +91,9 @@ window.addEventListener('load', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/** @desc When MetaMask is loaded as an extension, watch for network changes. */
|
||||
if ((window as any).web3) {
|
||||
setInterval(handleMetaMaskPolling.bind(null, store), METAMASK_POLLING_INTERVAL);
|
||||
}
|
||||
|
||||
export default store;
|
||||
|
@ -234,7 +234,8 @@ describe('swap reducer', () => {
|
||||
maxLimit: 7.04575258,
|
||||
apiPubKey:
|
||||
'0ca1ccd50b708a3f8c02327f0caeeece06d3ddc1b0ac749a987b453ee0f4a29bdb5da2e53bc35e57fb4bb7ae1f43c93bb098c3c4716375fc1001c55d8c94c160',
|
||||
minerFee: '1.05'
|
||||
minerFee: '1.05',
|
||||
sAddress: '0x055ed77933388642fdn4px9v73j4fa3582d10c4'
|
||||
};
|
||||
|
||||
const swapState = reducer.swapReducer(
|
||||
@ -254,8 +255,10 @@ describe('swap reducer', () => {
|
||||
validFor: swapState.validFor,
|
||||
orderTimestampCreatedISOString: swapState.orderTimestampCreatedISOString,
|
||||
paymentAddress: mockedShapeshiftOrder.deposit,
|
||||
paymentId: mockedShapeshiftOrder.deposit,
|
||||
shapeshiftOrderStatus: 'no_deposits',
|
||||
orderId: mockedShapeshiftOrder.orderId
|
||||
orderId: mockedShapeshiftOrder.orderId,
|
||||
xmrPaymentAddress: mockedShapeshiftOrder.sAddress
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -42,7 +42,9 @@ export const INITIAL_STATE: types.SwapState = {
|
||||
paymentAddress: null,
|
||||
validFor: null,
|
||||
orderId: null,
|
||||
showLiteSend: false
|
||||
showLiteSend: false,
|
||||
paymentId: null,
|
||||
xmrPaymentAddress: null
|
||||
};
|
||||
|
||||
export function swapReducer(state: types.SwapState = INITIAL_STATE, action: types.SwapAction) {
|
||||
@ -69,22 +71,19 @@ export function swapReducer(state: types.SwapState = INITIAL_STATE, action: type
|
||||
isFetchingRates: false
|
||||
};
|
||||
case types.SwapActions.LOAD_SHAPESHIFT_RATES_SUCCEEDED:
|
||||
const {
|
||||
entities: { providerRates: normalizedProviderRates, options: normalizedOptions }
|
||||
} = normalize(action.payload, [providerRate]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shapeshiftRates: {
|
||||
byId: normalize(action.payload, [providerRate]).entities.providerRates,
|
||||
allIds: allIds(normalize(action.payload, [providerRate]).entities.providerRates)
|
||||
byId: normalizedProviderRates,
|
||||
allIds: allIds(normalizedProviderRates)
|
||||
},
|
||||
options: {
|
||||
byId: Object.assign(
|
||||
{},
|
||||
normalize(action.payload, [providerRate]).entities.options,
|
||||
state.options.byId
|
||||
),
|
||||
allIds: [
|
||||
...allIds(normalize(action.payload, [providerRate]).entities.options),
|
||||
...state.options.allIds
|
||||
]
|
||||
byId: { ...normalizedOptions, ...state.options.byId },
|
||||
allIds: [...allIds(normalizedOptions), ...state.options.allIds]
|
||||
},
|
||||
isFetchingRates: false
|
||||
};
|
||||
@ -151,8 +150,8 @@ export function swapReducer(state: types.SwapState = INITIAL_STATE, action: type
|
||||
};
|
||||
case types.SwapActions.SHAPESHIFT_ORDER_CREATE_SUCCEEDED:
|
||||
const currDate = Date.now();
|
||||
|
||||
const secondsRemaining = Math.floor((+new Date(action.payload.expiration) - currDate) / 1000);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shapeshiftOrder: {
|
||||
@ -166,7 +165,10 @@ export function swapReducer(state: types.SwapState = INITIAL_STATE, action: type
|
||||
orderTimestampCreatedISOString: new Date(currDate).toISOString(),
|
||||
paymentAddress: action.payload.deposit,
|
||||
shapeshiftOrderStatus: 'no_deposits',
|
||||
orderId: action.payload.orderId
|
||||
orderId: action.payload.orderId,
|
||||
// For XMR swaps
|
||||
paymentId: action.payload.deposit,
|
||||
xmrPaymentAddress: action.payload.sAddress
|
||||
};
|
||||
case types.SwapActions.BITY_ORDER_STATUS_SUCCEEDED:
|
||||
return {
|
||||
|
@ -24,6 +24,18 @@ export interface SwapState {
|
||||
validFor: number | null;
|
||||
orderId: string | null;
|
||||
showLiteSend: boolean;
|
||||
/**
|
||||
* @desc
|
||||
* For XMR swaps, the "deposit" property in the response
|
||||
* actually refers to the "paymentId", not the payment address.
|
||||
*/
|
||||
paymentId: string | null;
|
||||
/**
|
||||
* @desc
|
||||
* For XMR swap, the actual payment address is the "sAddress"
|
||||
* property in the response.
|
||||
*/
|
||||
xmrPaymentAddress: string | null;
|
||||
}
|
||||
|
||||
export enum SwapActions {
|
||||
@ -208,6 +220,7 @@ export interface ShapeshiftOrderResponse {
|
||||
quotedRate: string;
|
||||
withdrawal: string;
|
||||
withdrawalAmount: string;
|
||||
sAddress?: string;
|
||||
}
|
||||
|
||||
export interface ShapeshiftStatusResponse {
|
||||
|
@ -3,6 +3,7 @@ import { WalletConfig } from 'libs/wallet/config';
|
||||
import { IWallet } from 'libs/wallet/IWallet';
|
||||
import { LedgerWallet } from 'libs/wallet/deterministic/ledger';
|
||||
import { TrezorWallet } from 'libs/wallet/deterministic/trezor';
|
||||
import { SafeTWallet } from 'libs/wallet/deterministic/safe-t';
|
||||
import Web3Wallet from 'libs/wallet/non-deterministic/web3';
|
||||
import ParitySignerWallet from 'libs/wallet/non-deterministic/parity';
|
||||
import { AppState } from 'features/reducers';
|
||||
@ -32,8 +33,9 @@ export const getWalletType = (state: AppState): IWalletType => {
|
||||
const isWeb3Wallet = wallet instanceof Web3Wallet;
|
||||
const isLedgerWallet = wallet instanceof LedgerWallet;
|
||||
const isTrezorWallet = wallet instanceof TrezorWallet;
|
||||
const isSafeTWallet = wallet instanceof SafeTWallet;
|
||||
const isParitySignerWallet = wallet instanceof ParitySignerWallet;
|
||||
const isHardwareWallet = isLedgerWallet || isTrezorWallet;
|
||||
const isHardwareWallet = isLedgerWallet || isTrezorWallet || isSafeTWallet;
|
||||
return { isWeb3Wallet, isHardwareWallet, isParitySignerWallet };
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MyCrypto</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="<%= htmlWebpackPlugin.options.metaCsp %>" >
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="description" content="MyCrypto is a free, open-source interface for interacting with the blockchain.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
@ -12,7 +13,7 @@
|
||||
<meta property="og:description" content="<%= htmlWebpackPlugin.options.appDescription %>">
|
||||
<meta property="og:site_name" content="<%= htmlWebpackPlugin.options.title %>">
|
||||
<meta property="og:type" content="<%= htmlWebpackPlugin.options.type %>">
|
||||
<meta property="og:image" content="<%= htmlWebpackPlugin.options.image %>">
|
||||
<meta property="og:image" content="/common/assets/images/link-preview.png">
|
||||
<meta name="twitter:site" content="<%= htmlWebpackPlugin.options.twitter.site %>">
|
||||
<meta name="twitter:creator" content="<%= htmlWebpackPlugin.options.twitter.creator %>">
|
||||
<meta name="google-site-verification" content="dRWkvANAUNAhNyMnTyc7M7S3lnucotMY8j8R-gsZhbo" />
|
||||
@ -71,40 +72,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var badBrowser = false;
|
||||
|
||||
try {
|
||||
// Let and const
|
||||
eval('let a = 1;');
|
||||
eval('const b = 1');
|
||||
|
||||
// Local storage
|
||||
window.localStorage.setItem('test', 'test');
|
||||
window.localStorage.removeItem('test');
|
||||
|
||||
// Flexbox
|
||||
var el = document.createElement('div');
|
||||
el.style.display = 'flex';
|
||||
if (el.style.display !== 'flex') {
|
||||
badBrowser = false;
|
||||
}
|
||||
} catch (err) {
|
||||
badBrowser = true;
|
||||
}
|
||||
|
||||
if (badBrowser) {
|
||||
var el = document.getElementsByClassName('BadBrowser')[0];
|
||||
el.className += ' is-open';
|
||||
// Dumb check for known mobile OS's. Not important to catch all, just
|
||||
// displays more appropriate information.
|
||||
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
||||
el.className += ' is-mobile';
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
@ -27,3 +27,12 @@ if (module.hot) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
consoleAdvertisement();
|
||||
}
|
||||
|
||||
const noOp = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
// disables drag-and-drop due to potential security issues by Cure53 recommendation
|
||||
document.addEventListener('dragover', noOp, false);
|
||||
document.addEventListener('drop', noOp, false);
|
||||
|
@ -102,9 +102,9 @@ export const NODE_CONFIGS: { [key in StaticNetworkIds]: RawNodeConfig[] } = {
|
||||
POA: [
|
||||
{
|
||||
name: makeNodeName('POA', 'core'),
|
||||
type: 'rpc',
|
||||
service: 'poa.network',
|
||||
url: 'https://core.poa.network'
|
||||
type: 'infura',
|
||||
service: 'poa.infura.io',
|
||||
url: 'https://poa.infura.io'
|
||||
}
|
||||
],
|
||||
|
||||
@ -168,6 +168,15 @@ export const NODE_CONFIGS: { [key in StaticNetworkIds]: RawNodeConfig[] } = {
|
||||
}
|
||||
],
|
||||
|
||||
RSK: [
|
||||
{
|
||||
name: makeNodeName('RSK', 'rsk_mainnet'),
|
||||
type: 'rpc',
|
||||
service: 'mycrypto.rsk.co',
|
||||
url: 'https://mycrypto.rsk.co/'
|
||||
}
|
||||
],
|
||||
|
||||
RSK_TESTNET: [
|
||||
{
|
||||
name: makeNodeName('RSK_TESTNET', 'rsk_testnet'),
|
||||
@ -186,6 +195,15 @@ export const NODE_CONFIGS: { [key in StaticNetworkIds]: RawNodeConfig[] } = {
|
||||
}
|
||||
],
|
||||
|
||||
GO_TESTNET: [
|
||||
{
|
||||
name: makeNodeName('GO_TESTNET', 'go_testnet'),
|
||||
type: 'rpc',
|
||||
service: 'testnet-rpc.gochain.io',
|
||||
url: 'https://testnet-rpc.gochain.io/'
|
||||
}
|
||||
],
|
||||
|
||||
EOSC: [
|
||||
{
|
||||
name: makeNodeName('EOSC', 'eosc'),
|
||||
|
@ -59,6 +59,12 @@ export function isValidBTCAddress(address: string): boolean {
|
||||
return WalletAddressValidator.validate(address, 'BTC');
|
||||
}
|
||||
|
||||
export function isValidXMRAddress(address: string): boolean {
|
||||
return !!address.match(
|
||||
/4[0-9AB][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{93}/
|
||||
);
|
||||
}
|
||||
|
||||
export function isValidHex(str: string): boolean {
|
||||
if (str === '') {
|
||||
return true;
|
||||
@ -133,11 +139,20 @@ export const validPositiveNumber = (num: number) => validNumber(num) && num !==
|
||||
|
||||
export const validDecimal = (input: string, decimal: number) => {
|
||||
const arr = input.split('.');
|
||||
|
||||
// Only a single decimal can exist.
|
||||
if (arr.length > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fractionPortion = arr[1];
|
||||
|
||||
if (!fractionPortion || fractionPortion.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const decimalLength = fractionPortion.length;
|
||||
|
||||
return decimalLength <= decimal;
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { HardwareWallet, ChainCodeResponse } from './hardware';
|
||||
const walletTypeNames = {
|
||||
[WalletTypes.LEDGER]: 'X_LEDGER',
|
||||
[WalletTypes.TREZOR]: 'X_TREZOR',
|
||||
[WalletTypes.SAFE_T]: 'X_SAFE_T',
|
||||
[WalletTypes.KEEPKEY]: 'X_KEEPKEY'
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { WalletTypes } from 'shared/enclave/client';
|
||||
import { makeEnclaveWallet } from './enclave';
|
||||
import { LedgerWallet as LedgerWalletWeb } from './ledger';
|
||||
import { TrezorWallet as TrezorWalletWeb } from './trezor';
|
||||
import { SafeTWallet as SafeTWalletWeb } from './safe-t';
|
||||
|
||||
function enclaveOrWallet<T>(type: WalletTypes, lib: T) {
|
||||
return process.env.BUILD_ELECTRON ? makeEnclaveWallet(type) : lib;
|
||||
@ -11,3 +12,4 @@ export * from './mnemonic';
|
||||
export * from './hardware';
|
||||
export const LedgerWallet = enclaveOrWallet(WalletTypes.LEDGER, LedgerWalletWeb);
|
||||
export const TrezorWallet = enclaveOrWallet(WalletTypes.TREZOR, TrezorWalletWeb);
|
||||
export const SafeTWallet = enclaveOrWallet(WalletTypes.SAFE_T, SafeTWalletWeb);
|
||||
|
116
common/libs/wallet/deterministic/safe-t.ts
Normal file
116
common/libs/wallet/deterministic/safe-t.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import BN from 'bn.js';
|
||||
import EthTx, { TxObj } from 'ethereumjs-tx';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
|
||||
import { translateRaw } from 'translations';
|
||||
import SafeTConnect from 'vendor/safe-t-connect';
|
||||
import { getTransactionFields } from 'libs/transaction';
|
||||
import { padLeftEven } from 'libs/values';
|
||||
import { stripHexPrefixAndLower } from 'libs/formatters';
|
||||
import { HardwareWallet, ChainCodeResponse } from './hardware';
|
||||
|
||||
export const SAFE_T_MINIMUM_FIRMWARE = '1.0.0';
|
||||
|
||||
export class SafeTWallet extends HardwareWallet {
|
||||
public static getChainCode(dpath: string): Promise<ChainCodeResponse> {
|
||||
return new Promise(resolve => {
|
||||
SafeTConnect.getXPubKey(
|
||||
dpath,
|
||||
res => {
|
||||
if (res.success) {
|
||||
resolve({
|
||||
publicKey: res.publicKey,
|
||||
chainCode: res.chainCode
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
SAFE_T_MINIMUM_FIRMWARE
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public signRawTransaction(tx: EthTx): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { chainId, ...strTx } = getTransactionFields(tx);
|
||||
// stripHexPrefixAndLower identical to ethFuncs.getNakedAddress
|
||||
const cleanedTx = mapValues(mapValues(strTx, stripHexPrefixAndLower), padLeftEven);
|
||||
|
||||
SafeTConnect.ethereumSignTx(
|
||||
// Args
|
||||
this.getPath(),
|
||||
cleanedTx.nonce,
|
||||
cleanedTx.gasPrice,
|
||||
cleanedTx.gasLimit,
|
||||
cleanedTx.to,
|
||||
cleanedTx.value,
|
||||
cleanedTx.data,
|
||||
chainId,
|
||||
// Callback
|
||||
result => {
|
||||
if (!result.success) {
|
||||
return reject(Error(result.error));
|
||||
}
|
||||
|
||||
// TODO: Explain what's going on here? Add tests? Adapted from:
|
||||
// https://github.com/kvhnuke/etherwallet/blob/v3.10.2.6/app/scripts/uiFuncs.js#L24
|
||||
const txToSerialize: TxObj = {
|
||||
...strTx,
|
||||
v: addHexPrefix(new BN(result.v).toString(16)),
|
||||
r: addHexPrefix(result.r.toString()),
|
||||
s: addHexPrefix(result.s)
|
||||
};
|
||||
const eTx = new EthTx(txToSerialize);
|
||||
const serializedTx = eTx.serialize();
|
||||
resolve(serializedTx);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public signMessage() {
|
||||
return Promise.reject(new Error('Signing via Safe-T mini not yet supported.'));
|
||||
}
|
||||
|
||||
public displayAddress(): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
SafeTConnect.ethereumGetAddress(
|
||||
`${this.dPath}/${this.index}`,
|
||||
res => {
|
||||
if (res.error) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
SAFE_T_MINIMUM_FIRMWARE
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public getWalletType(): string {
|
||||
return translateRaw('X_SAFE_T');
|
||||
}
|
||||
|
||||
// works, but returns a signature that can only be verified with a Safe-T mini device
|
||||
/*
|
||||
public signMessage = (message: string): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
SafeTConnect.ethereumSignMessage(
|
||||
this.getPath(),
|
||||
message,
|
||||
response => {
|
||||
if (response.success) {
|
||||
resolve(addHexPrefix(response.signature))
|
||||
} else{
|
||||
console.error(response.error)
|
||||
reject(response.error)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
@ -6,8 +6,9 @@ import { INode } from 'libs/nodes/INode';
|
||||
import { IFullWallet } from '../IWallet';
|
||||
|
||||
export default class Web3Wallet implements IFullWallet {
|
||||
public network: string;
|
||||
|
||||
private address: string;
|
||||
private network: string;
|
||||
|
||||
constructor(address: string, network: string) {
|
||||
this.address = address;
|
||||
|
@ -67,13 +67,21 @@ export function translateRaw(key: string, variables?: { [name: string]: string }
|
||||
const translatedString =
|
||||
(repository[language] && repository[language][key]) || repository[fallbackLanguage][key] || key;
|
||||
|
||||
if (!!variables) {
|
||||
// Find each variable and replace it in the translated string
|
||||
let str = translatedString;
|
||||
Object.keys(variables).forEach(v => {
|
||||
const re = new RegExp(v.replace('$', '\\$'), 'g');
|
||||
str = str.replace(re, variables[v]);
|
||||
/** @desc In RegExp, $foo is two "words", but __foo is only one "word."
|
||||
* Replace all occurences of '$' with '__' in the entire string and each variable,
|
||||
* then iterate over each variable, replacing the '__variable' in the
|
||||
* translation key with the variable's value.
|
||||
*/
|
||||
if (variables) {
|
||||
let str = translatedString.replace(/\$/g, '__');
|
||||
|
||||
Object.keys(variables).forEach(variable => {
|
||||
const singleWordVariable = variable.replace(/\$/g, '__');
|
||||
const re = new RegExp(`\\b${singleWordVariable}\\b`, 'g');
|
||||
|
||||
str = str.replace(re, variables[variable]);
|
||||
});
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
@ -282,6 +282,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Connexion au TREZOR ",
|
||||
"ADD_TREZOR_SELECT": "Ceci est une _seed_ TREZOR ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Connexion au Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Réouvrir MyCrypto sur une connexion sécurisée (SSL) ",
|
||||
"ADD_DIGITALBITBOX_0B": "Réouvrir MyCrypto avec [Chrome](https://www.google.com/chrome/browser/desktop/) ou [Opera](https://www.opera.com/) ",
|
||||
|
@ -295,6 +295,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Zu TREZOR Verbinden ",
|
||||
"ADD_TREZOR_SELECT": "Dies ist ein TREZOR seed ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Zu Safe-T mini Verbinden ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ",
|
||||
"ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ",
|
||||
|
@ -296,6 +296,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Συνδεθείτε στο TREZOR ",
|
||||
"ADD_TREZOR_SELECT": "Αυτός είναι σπόρος του TREZOR ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Συνδεθείτε στο Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ",
|
||||
"ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ",
|
||||
|
@ -81,6 +81,9 @@
|
||||
"ORDER_TREZOR": "Don’t have a TREZOR? Order one now!",
|
||||
"HOWTO_TREZOR": "How to use TREZOR with MyCrypto",
|
||||
"X_KEEPKEY": "KeepKey",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Connect to Safe-T mini ",
|
||||
"ORDER_SAFE_T": "Don’t have a Safe-T mini? Order one now!",
|
||||
"UNLOCK_WALLET": "Unlock your",
|
||||
"X_PARITYSIGNER": "Parity Signer ",
|
||||
"ADD_PARITY_DESC": "Connect & sign via your Parity Signer mobile app ",
|
||||
@ -460,6 +463,7 @@
|
||||
"LEDGER_WRONG_APP": "Wrong application selected on your device",
|
||||
"LEDGER_LOCKED": "Your Ledger device is locked",
|
||||
"TREZOR_REFERAL": "Buy a TREZOR",
|
||||
"SAFE_T_REFERAL": "Buy a Safe-T mini",
|
||||
"KEEPKEY_REFERRAL": "Buy a Keepkey",
|
||||
"STEELY_REFERRAL": "Get a Steely",
|
||||
"ETHERCARD_REFERAL": "Get an ether.card",
|
||||
@ -643,7 +647,7 @@
|
||||
"SHAPESHIFT_PROMO_2": "& Tokens",
|
||||
"COINBASE_PROMO_SMALL": "It’s now easier to get more ETH",
|
||||
"COINBASE_PROMO": "Buy ETH with USD",
|
||||
"SIMPLEX_PROMO": "Buy ETH with EUR",
|
||||
"SIMPLEX_PROMO": "Buy ETH with Credit Card",
|
||||
"TESTNET": "Testnet",
|
||||
"ENCLAVE_LEDGER_FAIL": "Failed to connect to Ledger",
|
||||
"ENCLAVE_LEDGER_IN_USE": "Your Ledger is currently in use with another application. Please wait, or close other wallet applications before trying again.",
|
||||
@ -660,7 +664,11 @@
|
||||
"NETWORK_2": "network",
|
||||
"PROVIDED_BY": "provided by",
|
||||
"YOU_ARE_INTERACTING": "You are interacting with the",
|
||||
"ANNOUNCEMENT_MESSAGE": "Welcome to the new MyCrypto. We hope you like it! If it's urgent and you need the old site, you can still use [MyCrypto Legacy](https://legacy.mycrypto.com)",
|
||||
"U2F_NOT_SUPPORTED": "The U2F standard that hardware wallets use does not seem to be supported by your browser. Please try again using Google Chrome."
|
||||
"USING_PAYMENT_ID": "Using the required Payment ID of:",
|
||||
"PAYMENT_ID_WARNING": "Don't forget to send your XMR with the payment ID [[?]](https://getmonero.org/resources/moneropedia/paymentid.html) above, or you WILL lose your funds.",
|
||||
"WHAT_IS_PAYMENT_ID": "what's a payment ID?",
|
||||
"ANNOUNCEMENT_MESSAGE": "MyCrypto.com no longer allows the use of private keys, mnemonics, or keystore files in the browser. To continue using them, please download the [MyCrypto Desktop App](https://download.mycrypto.com).",
|
||||
"U2F_NOT_SUPPORTED": "The U2F standard that hardware wallets use does not seem to be supported by your browser. Please try again using Google Chrome.",
|
||||
"SIMILAR_TRANSACTION_ERROR": "This transaction is very similar to a recent transaction. Please wait a few moments and try again, or click 'Advanced' and manually set the nonce to a new value."
|
||||
}
|
||||
}
|
||||
|
@ -295,6 +295,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Conectar a TREZOR ",
|
||||
"ADD_TREZOR_SELECT": "Esto es una semilla TREZOR ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Conectar a Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Volver a abrir MyCrypto en una conexión segura (SSL) ",
|
||||
"ADD_DIGITALBITBOX_0B": "Volver a abrir MyCrypto usando [Chrome](https://www.google.com/chrome/browser/desktop/) u [Opera](https://www.opera.com/) ",
|
||||
|
@ -296,6 +296,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Connect to TREZOR ",
|
||||
"ADD_TREZOR_SELECT": "This is a TREZOR seed ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Connect to Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ",
|
||||
"ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ",
|
||||
|
@ -80,6 +80,9 @@
|
||||
"ADD_TREZOR_SCAN": "Connexion au TREZOR ",
|
||||
"ORDER_TREZOR": "Pas de TREZOR ? Achetez-en un maintenant !",
|
||||
"HOWTO_TREZOR": "Comment utiliser TREZOR avec MyCrypto",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Connexion au Safe-T mini ",
|
||||
"ORDER_SAFE_T": "Pas de Safe-T mini ? Achetez-en un maintenant !",
|
||||
"X_KEEPKEY": "KeepKey",
|
||||
"UNLOCK_WALLET": "Déverrouillez votre",
|
||||
"X_PARITYSIGNER": "Parity Signer ",
|
||||
@ -460,6 +463,7 @@
|
||||
"LEDGER_WRONG_APP": "Mauvaise application sélectionnée sur votre appareil",
|
||||
"LEDGER_LOCKED": "Votre appareil Ledger est verrouillé",
|
||||
"TREZOR_REFERAL": "Acheter un TREZOR",
|
||||
"SAFE_T_REFERAL": "Acheter un Safe-T mini",
|
||||
"KEEPKEY_REFERRAL": "Acheter un Keepkey",
|
||||
"STEELY_REFERRAL": "Acheter un Steely",
|
||||
"ETHERCARD_REFERAL": "Acheter une ether.card",
|
||||
|
@ -136,6 +136,8 @@
|
||||
"ADD_METAMASK": "Connect to MetaMask ",
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Connect to TREZOR ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Connect to Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ",
|
||||
"ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ",
|
||||
|
@ -296,6 +296,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Connect to TREZOR ",
|
||||
"ADD_TREZOR_SELECT": "This is a TREZOR seed ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Connect to Safe-T mini ",
|
||||
"ADD_METAMASK": "Connect to MetaMask ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ",
|
||||
|
@ -160,6 +160,8 @@
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Hubungkan ke TREZOR ",
|
||||
"ADD_TREZOR_SELECT": "Ini adalah TREZOR seed ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Hubungkan ke Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Buka kembali MyCrypto melalui koneksi (SSL) yang aman ",
|
||||
"ADD_DIGITALBITBOX_0B": "Buka kembali MyCrypto menggunakan [Chrome](https://www.google.com/chrome/browser/desktop/) atau [Opera](https://www.opera.com/) ",
|
||||
|
@ -244,6 +244,8 @@
|
||||
"ADD_METAMASK": "Collegati a MetaMask ",
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Collegati al TREZOR ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Collegati al Safe-T mini ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Riapri MyCrypto su una connessione sicura (SSL) ",
|
||||
"ADD_DIGITALBITBOX_0B": "Riapri MyCrypto utilizzando [Chrome](https://www.google.com/chrome/browser/desktop/) o [Opera](https://www.opera.com/) ",
|
||||
|
@ -61,6 +61,8 @@
|
||||
"ADD_METAMASK": "MetaMaskに接続 ",
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "TREZORに接続する ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Safe-T miniに接続する ",
|
||||
"X_PARITYSIGNER": "Parity Signer ",
|
||||
"ADD_PARITY_DESC": "Parity Signerモバイルアプリ経由で接続て署名 ",
|
||||
"ADD_PARITY_1": "処理が取り消されました ",
|
||||
@ -394,6 +396,7 @@
|
||||
"LEDGER_WRONG_APP": "デバイス上で誤ったアプリが選択されています。",
|
||||
"LEDGER_LOCKED": "Ledgerデバイスがロックされています",
|
||||
"TREZOR_REFERAL": "TREZORを購入",
|
||||
"SAFE_T_REFERAL": "Safe-T miniを購入",
|
||||
"KEEPKEY_REFERRAL": "Keepkeyを購入",
|
||||
"STEELY_REFERRAL": "Steelyを購入",
|
||||
"ETHERCARD_REFERAL": "ether.cardを購入",
|
||||
|
@ -78,6 +78,8 @@
|
||||
"X_HARDWARE_WALLET": "하드웨어 지갑",
|
||||
"X_HARDWARE_WALLET_2": "하드웨어 지갑 ",
|
||||
"ADD_TREZOR_SCAN": "TREZOR에 연결하기 ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Safe-T mini 에 연결하기 ",
|
||||
"X_KEEPKEY": "KeepKey 지갑",
|
||||
"X_PARITYSIGNER": "Parity Signer ",
|
||||
"ADD_PARITY_DESC": "Parity Signer 모바일 앱을 통해 연결 및 서명 ",
|
||||
|
@ -134,6 +134,8 @@
|
||||
"ADD_LEDGER_SCAN": "Verbind met Ledger Wallet ",
|
||||
"X_TREZOR": "TREZOR ",
|
||||
"ADD_TREZOR_SCAN": "Verbind met TREZOR ",
|
||||
"X_SAFE_T": "Safe-T mini ",
|
||||
"ADD_SAFE_T_SCAN": "Verbind met Safe-T mini ",
|
||||
"ADD_METAMASK": "Verbind met MetaMask ",
|
||||
"X_DIGITALBITBOX": "Digital Bitbox ",
|
||||
"ADD_DIGITALBITBOX_0A": "Her-open MyCrypto met een veilige (SSL) verbinding ",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user