Merge branch 'develop' into fix-gas-slider
This commit is contained in:
commit
6cc8cafa64
|
@ -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() {
|
||||
|
|
|
@ -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;
|
|
@ -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">
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -12,12 +12,6 @@ export const discordURL = 'https://discord.gg/VSaTXEA';
|
|||
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.
|
||||
|
@ -44,7 +38,9 @@ export const etherChainExplorerInst = makeExplorer({
|
|||
export const donationAddressMap = {
|
||||
BTC: '32oirLEzZRhi33RCXDF9WHJjEb8RsrSss3',
|
||||
ETH: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
||||
REP: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520'
|
||||
REP: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
||||
XMR:
|
||||
'4GdoN7NCTi8a5gZug7PrwZNKjvHFmKeV11L6pNJPgj5QNEHsN6eeX3DaAQFwZ1ufD4LYCZKArktt113W7QjWvQ7CW7F7tDFvS511SNfZV7'
|
||||
};
|
||||
|
||||
export const gasEstimateCacheTime = 60000;
|
||||
|
|
|
@ -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,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -660,6 +660,9 @@
|
|||
"NETWORK_2": "network",
|
||||
"PROVIDED_BY": "provided by",
|
||||
"YOU_ARE_INTERACTING": "You are interacting with the",
|
||||
"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": "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."
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ exports[`render snapshot 1`] = `
|
|||
}
|
||||
outputTx={null}
|
||||
paymentAddress={null}
|
||||
paymentId={null}
|
||||
provider="shapeshift"
|
||||
restartSwap={[Function]}
|
||||
secondsRemaining={null}
|
||||
|
@ -100,5 +101,6 @@ exports[`render snapshot 1`] = `
|
|||
stopPollBityOrderStatus={[Function]}
|
||||
stopPollShapeshiftOrderStatus={[Function]}
|
||||
swapProvider={[Function]}
|
||||
xmrPaymentAddress={null}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Sitemap: https://mycrypto.com/sitemap.xml
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url><loc>https://mycrypto.com</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/account</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/generate</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/swap</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/contracts</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/ens</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/sign-and-verify-message</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/tx-status</loc><priority>0.5</priority></url>
|
||||
<url><loc>https://mycrypto.com/pushTx</loc><priority>0.5</priority></url>
|
||||
</urlset>
|
Loading…
Reference in New Issue