Merge pull request #2404 from 0xProject/feature/instant/fortmatic-integration
Formatic integration into instant
This commit is contained in:
commit
282a351859
@ -2,6 +2,8 @@ INSTANT_ROLLBAR_PUBLISH_TOKEN=
|
||||
INSTANT_ROLLBAR_CLIENT_TOKEN=
|
||||
INSTANT_HEAP_ANALYTICS_ID_PRODUCTION=
|
||||
INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT=
|
||||
INSTANT_FORTMATIC_API_KEY_PRODUCTION=
|
||||
INSTANT_FORTMATIC_API_KEY_DEVELOPMENT=
|
||||
INSTANT_INFURA_PROJECT_ID_PRODUCTION=
|
||||
INSTANT_INFURA_PROJECT_ID_DEVELOPMENT=
|
||||
# if you want to report to heap or rollbar when building in development mode, you can use the following:
|
||||
|
@ -53,6 +53,7 @@
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bowser": "^1.9.4",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"fortmatic": "^1.0.1",
|
||||
"lodash": "^4.17.11",
|
||||
"polished": "^1.9.2",
|
||||
"react": "^16.5.2",
|
||||
|
@ -199,7 +199,7 @@
|
||||
};
|
||||
const render = renderOptionsOverrides => {
|
||||
const renderOptionsDefaults = {
|
||||
orderSource: 'https://api.0x.org/sra/',
|
||||
orderSource: 'https://api.0x.org/sra',
|
||||
onClose: () => {
|
||||
console.log('0x Instant Closed');
|
||||
},
|
||||
|
3
packages/instant/src/assets/icons/chevronRight.svg
generated
Normal file
3
packages/instant/src/assets/icons/chevronRight.svg
generated
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="7" height="13" viewBox="0 0 7 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1.5L6 6.5L1 11.5" stroke="#AAAAAA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 216 B |
3
packages/instant/src/assets/icons/phone.svg
generated
Normal file
3
packages/instant/src/assets/icons/phone.svg
generated
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.55139 1.67194C5.93755 2.05783 6.32503 2.44262 6.70943 2.83027C7.38729 3.51398 7.38861 4.31146 6.71338 4.9899C6.22904 5.4766 5.74755 5.96616 5.25376 6.44298C5.12328 6.56883 5.11296 6.67249 5.17908 6.83107C5.50329 7.60966 5.97204 8.29908 6.49943 8.94743C7.5595 10.2505 8.75839 11.4071 10.1881 12.3045C10.4943 12.4967 10.833 12.6366 11.152 12.8096C11.3161 12.8986 11.4285 12.8703 11.5599 12.7345C12.0429 12.2353 12.5376 11.7471 13.0312 11.2579C13.6776 10.6175 14.489 10.6162 15.1383 11.2593C15.9317 12.0451 16.7216 12.8347 17.5082 13.6271C18.1681 14.2917 18.1628 15.1039 17.4992 15.7733C17.0509 16.2258 16.5771 16.6552 16.1531 17.1291C15.5361 17.819 14.762 18.0423 13.8766 17.9936C12.5896 17.9229 11.4024 17.4959 10.2591 16.9402C7.71765 15.7052 5.54502 13.9937 3.72758 11.8305C2.38372 10.2312 1.27313 8.4906 0.542776 6.52468C0.189567 5.5739 -0.0630384 4.60006 0.0138415 3.56999C0.0612874 2.93438 0.300055 2.39365 0.768583 1.9478C1.2716 1.46878 1.75089 0.964948 2.24643 0.477807C2.8953 -0.16022 3.70693 -0.158902 4.35734 0.479565C4.75887 0.873582 5.15359 1.27419 5.55139 1.67194Z" fill="#3CB34F"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -13,7 +13,7 @@ import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { oc } from 'ts-optchain';
|
||||
|
||||
import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants';
|
||||
import { DEFAULT_AFFILIATE_INFO, WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants';
|
||||
import { ColorOption } from '../style/theme';
|
||||
import { AffiliateInfo, Asset, ZeroExInstantError } from '../types';
|
||||
import { analytics } from '../util/analytics';
|
||||
@ -77,7 +77,7 @@ export class BuyButton extends React.PureComponent<BuyButtonProps> {
|
||||
const {
|
||||
swapQuote,
|
||||
swapQuoteConsumer,
|
||||
affiliateInfo,
|
||||
affiliateInfo = DEFAULT_AFFILIATE_INFO,
|
||||
accountAddress,
|
||||
accountEthBalanceInWei,
|
||||
web3Wrapper,
|
||||
@ -132,6 +132,14 @@ export class BuyButton extends React.PureComponent<BuyButtonProps> {
|
||||
this.props.onSignatureDenied(swapQuote);
|
||||
return;
|
||||
}
|
||||
// Fortmatic specific error handling
|
||||
if (e.message && e.message.includes('Fortmatic:')) {
|
||||
if (e.message.includes('User denied transaction.')) {
|
||||
analytics.trackBuySignatureDenied(swapQuote);
|
||||
this.props.onSignatureDenied(swapQuote);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
const startTimeUnix = new Date().getTime();
|
||||
|
@ -2,10 +2,11 @@ import * as React from 'react';
|
||||
|
||||
export interface CoinbaseWalletLogoProps {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export const CoinbaseWalletLogo: React.StatelessComponent<CoinbaseWalletLogoProps> = ({ width }) => (
|
||||
<svg width={width} viewBox="0 0 51 51" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
export const CoinbaseWalletLogo: React.StatelessComponent<CoinbaseWalletLogoProps> = ({ width, height }) => (
|
||||
<svg width={width} height={height} viewBox="0 0 51 51" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="25.5" cy="25.5" r="25.5" fill="#3263E9" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
@ -20,4 +21,5 @@ CoinbaseWalletLogo.displayName = 'CoinbaseWalletLogo';
|
||||
|
||||
CoinbaseWalletLogo.defaultProps = {
|
||||
width: 164,
|
||||
height: 164,
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { oc } from 'ts-optchain';
|
||||
|
||||
import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants';
|
||||
import { ColorOption } from '../style/theme';
|
||||
import { BaseCurrency } from '../types';
|
||||
import { Account, AccountState, BaseCurrency } from '../types';
|
||||
import { format } from '../util/format';
|
||||
|
||||
import { AmountPlaceholder } from './amount_placeholder';
|
||||
@ -25,10 +25,15 @@ export interface OrderDetailsProps {
|
||||
baseCurrency: BaseCurrency;
|
||||
onBaseCurrencySwitchEth: () => void;
|
||||
onBaseCurrencySwitchUsd: () => void;
|
||||
account: Account;
|
||||
}
|
||||
export class OrderDetails extends React.PureComponent<OrderDetailsProps> {
|
||||
public render(): React.ReactNode {
|
||||
const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice();
|
||||
const { state } = this.props.account;
|
||||
if (state !== AccountState.Ready) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Container width="100%" flexGrow={1} padding="20px 20px 0px 20px">
|
||||
<Container marginBottom="10px">{this._renderHeader()}</Container>
|
||||
@ -36,6 +41,7 @@ export class OrderDetails extends React.PureComponent<OrderDetailsProps> {
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderRows(): React.ReactNode {
|
||||
const { swapQuoteInfo } = this.props;
|
||||
|
@ -1,19 +1,16 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
|
||||
import PhoneIconSvg from '../assets/icons/phone.svg';
|
||||
import { ColorOption } from '../style/theme';
|
||||
import { Account, AccountState, Network } from '../types';
|
||||
import { Account, AccountState, Network, ProviderType } from '../types';
|
||||
import { envUtil } from '../util/env';
|
||||
|
||||
import { CoinbaseWalletLogo } from './coinbase_wallet_logo';
|
||||
import { MetaMaskLogo } from './meta_mask_logo';
|
||||
import { PaymentMethodDropdown } from './payment_method_dropdown';
|
||||
import { SectionHeader } from './section_header';
|
||||
import { Circle } from './ui/circle';
|
||||
import { Container } from './ui/container';
|
||||
import { Flex } from './ui/flex';
|
||||
import { Icon } from './ui/icon';
|
||||
import { Text } from './ui/text';
|
||||
import { WalletPrompt } from './wallet_prompt';
|
||||
|
||||
export interface PaymentMethodProps {
|
||||
@ -21,20 +18,20 @@ export interface PaymentMethodProps {
|
||||
network: Network;
|
||||
walletDisplayName: string;
|
||||
onInstallWalletClick: () => void;
|
||||
onUnlockWalletClick: () => void;
|
||||
onUnlockWalletClick: (providerType: ProviderType) => void;
|
||||
}
|
||||
|
||||
export class PaymentMethod extends React.PureComponent<PaymentMethodProps> {
|
||||
public render(): React.ReactNode {
|
||||
const marginBottom = this.props.account.state !== AccountState.Ready ? '77px' : null;
|
||||
return (
|
||||
<Container width="100%" height="120px" padding="20px 20px 0px 20px">
|
||||
<Container width="100%" height="100%" padding="20px 20px 0px 20px" marginBottom={marginBottom}>
|
||||
<Container marginBottom="12px">
|
||||
<Flex justify="space-between">
|
||||
<SectionHeader>{this._renderTitleText()}</SectionHeader>
|
||||
{this._renderTitleLabel()}
|
||||
</Flex>
|
||||
</Container>
|
||||
{this._renderMainContent()}
|
||||
<Container>{this._renderMainContent()}</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@ -50,52 +47,88 @@ export class PaymentMethod extends React.PureComponent<PaymentMethodProps> {
|
||||
return 'payment method';
|
||||
}
|
||||
};
|
||||
private readonly _renderTitleLabel = (): React.ReactNode => {
|
||||
const { account } = this.props;
|
||||
if (account.state === AccountState.Ready || account.state === AccountState.Locked) {
|
||||
const circleColor: ColorOption = account.state === AccountState.Ready ? ColorOption.green : ColorOption.red;
|
||||
return (
|
||||
<Flex align="center">
|
||||
<Circle diameter={8} color={circleColor} />
|
||||
<Container marginLeft="5px">
|
||||
<Text fontColor={ColorOption.darkGrey} fontSize="12px" lineHeight="30px">
|
||||
{this.props.walletDisplayName}
|
||||
</Text>
|
||||
</Container>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
private readonly _renderMainContent = (): React.ReactNode => {
|
||||
const { account, network } = this.props;
|
||||
const isMobile = envUtil.isMobileOperatingSystem();
|
||||
const logo = isMobile ? <CoinbaseWalletLogo width={22} /> : <MetaMaskLogo width={19} height={18} />;
|
||||
const primaryColor = isMobile ? ColorOption.darkBlue : ColorOption.darkOrange;
|
||||
const secondaryColor = isMobile ? ColorOption.lightBlue : ColorOption.lightOrange;
|
||||
const metamaskLogo = <MetaMaskLogo width={23} height={22} />;
|
||||
const logo = isMobile ? <CoinbaseWalletLogo width={22} height={22} /> : metamaskLogo;
|
||||
const primaryColor = ColorOption.grey;
|
||||
const secondaryColor = ColorOption.whiteBackground;
|
||||
const colors = { primaryColor, secondaryColor };
|
||||
const onUnlockGenericWallet = () => {
|
||||
this.props.onUnlockWalletClick(ProviderType.MetaMask);
|
||||
};
|
||||
const onUnlockFormatic = () => this.props.onUnlockWalletClick(ProviderType.Fortmatic);
|
||||
switch (account.state) {
|
||||
case AccountState.Loading:
|
||||
return null;
|
||||
case AccountState.Locked:
|
||||
return (
|
||||
<Flex direction="column">
|
||||
<WalletPrompt
|
||||
onClick={this.props.onUnlockWalletClick}
|
||||
onClick={onUnlockGenericWallet}
|
||||
display="flex"
|
||||
alignText={'flex-start'}
|
||||
marginLeft="16px"
|
||||
fontWeight="normal"
|
||||
padding="15px 18px"
|
||||
image={
|
||||
<Container position="relative" top="2px">
|
||||
<Icon width={13} icon="lock" color={ColorOption.black} />
|
||||
<Container position="relative" display="flex">
|
||||
{logo}
|
||||
</Container>
|
||||
}
|
||||
{...colors}
|
||||
>
|
||||
Click to Connect {this.props.walletDisplayName}
|
||||
{isMobile ? 'Coinbase Wallet' : 'MetaMask'}
|
||||
</WalletPrompt>
|
||||
<WalletPrompt
|
||||
onClick={onUnlockFormatic}
|
||||
marginTop="14px"
|
||||
marginLeft="19px"
|
||||
fontWeight="normal"
|
||||
padding="15px 18px"
|
||||
image={
|
||||
<Container position="relative" display="flex">
|
||||
<PhoneIconSvg />
|
||||
</Container>
|
||||
}
|
||||
display="flex"
|
||||
{...colors}
|
||||
>
|
||||
Use phone number
|
||||
</WalletPrompt>
|
||||
</Flex>
|
||||
);
|
||||
case AccountState.None:
|
||||
return (
|
||||
<WalletPrompt onClick={this.props.onInstallWalletClick} image={logo} {...colors}>
|
||||
<Flex direction="column" justify="space-between" height="100%">
|
||||
<WalletPrompt
|
||||
onClick={this.props.onInstallWalletClick}
|
||||
image={logo}
|
||||
{...colors}
|
||||
fontWeight="normal"
|
||||
marginLeft="19px"
|
||||
padding="15px 18px"
|
||||
>
|
||||
{isMobile ? 'Install Coinbase Wallet' : 'Install MetaMask'}
|
||||
</WalletPrompt>
|
||||
<WalletPrompt
|
||||
onClick={onUnlockFormatic}
|
||||
marginTop="14px"
|
||||
fontWeight="normal"
|
||||
marginLeft="19px"
|
||||
padding="15px 18px"
|
||||
image={
|
||||
<Container position="relative" display="flex">
|
||||
<PhoneIconSvg />
|
||||
</Container>
|
||||
}
|
||||
display="flex"
|
||||
{...colors}
|
||||
>
|
||||
Use phone number
|
||||
</WalletPrompt>
|
||||
</Flex>
|
||||
);
|
||||
case AccountState.Ready:
|
||||
return (
|
||||
|
@ -75,6 +75,7 @@ export const Container = styled.div<ContainerProps>`
|
||||
${props => cssRuleIfExists(props, 'opacity')}
|
||||
${props => cssRuleIfExists(props, 'cursor')}
|
||||
${props => cssRuleIfExists(props, 'overflow')}
|
||||
${props => cssRuleIfExists(props, 'alignSelf')}
|
||||
${props => (props.overflow === 'scroll' ? `-webkit-overflow-scrolling: touch` : '')};
|
||||
${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
|
||||
${props => props.display && stylesForMedia<string>('display', props.display)}
|
||||
|
@ -19,6 +19,7 @@ interface IconInfoMapping {
|
||||
failed: IconInfo;
|
||||
success: IconInfo;
|
||||
chevron: IconInfo;
|
||||
chevronRight: IconInfo;
|
||||
search: IconInfo;
|
||||
lock: IconInfo;
|
||||
}
|
||||
@ -52,6 +53,14 @@ const ICONS: IconInfoMapping = {
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
},
|
||||
chevronRight: {
|
||||
viewBox: '0 0 7 13',
|
||||
path: 'M1 1.5L6 6.5L1 11.5',
|
||||
strokeOpacity: 0.5,
|
||||
strokeWidth: 1.5,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
},
|
||||
search: {
|
||||
viewBox: '0 0 14 14',
|
||||
fillRule: 'evenodd',
|
||||
@ -89,6 +98,7 @@ const PlainIcon: React.StatelessComponent<IconProps> = props => {
|
||||
viewBox={iconInfo.viewBox}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke={props.stroke}
|
||||
>
|
||||
<path
|
||||
d={iconInfo.path}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import ChevronRightSvg from '../assets/icons/chevronRight.svg';
|
||||
import { ColorOption } from '../style/theme';
|
||||
|
||||
import { Container } from './ui/container';
|
||||
@ -7,10 +8,16 @@ import { Flex } from './ui/flex';
|
||||
import { Text } from './ui/text';
|
||||
|
||||
export interface WalletPromptProps {
|
||||
image: React.ReactNode;
|
||||
image?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
primaryColor: ColorOption;
|
||||
secondaryColor: ColorOption;
|
||||
marginTop?: string;
|
||||
display?: string;
|
||||
alignText?: string;
|
||||
marginLeft?: string;
|
||||
fontWeight?: string;
|
||||
padding?: string;
|
||||
}
|
||||
|
||||
export const WalletPrompt: React.StatelessComponent<WalletPromptProps> = ({
|
||||
@ -19,24 +26,36 @@ export const WalletPrompt: React.StatelessComponent<WalletPromptProps> = ({
|
||||
children,
|
||||
secondaryColor,
|
||||
primaryColor,
|
||||
marginTop,
|
||||
display,
|
||||
alignText,
|
||||
marginLeft = '10px',
|
||||
fontWeight = '500',
|
||||
padding = '10px',
|
||||
}) => (
|
||||
<Container
|
||||
padding="10px"
|
||||
border={`1px solid ${primaryColor}`}
|
||||
padding={padding}
|
||||
border={`1px solid`}
|
||||
borderColor={ColorOption.feintGrey}
|
||||
backgroundColor={secondaryColor}
|
||||
width="100%"
|
||||
borderRadius="4px"
|
||||
onClick={onClick}
|
||||
cursor={onClick ? 'pointer' : undefined}
|
||||
boxShadowOnHover={!!onClick}
|
||||
marginTop={marginTop}
|
||||
display={display}
|
||||
>
|
||||
<Flex>
|
||||
<Flex width="100%">
|
||||
{image}
|
||||
<Container marginLeft="10px">
|
||||
<Text fontSize="16px" fontColor={primaryColor} fontWeight="500">
|
||||
<Container marginLeft={marginLeft} display={display} width="100%" alignSelf={alignText}>
|
||||
<Text fontSize="16px" fontColor={primaryColor} fontWeight={fontWeight}>
|
||||
{children}
|
||||
</Text>
|
||||
</Container>
|
||||
<Container position="relative" top="2px" display={display}>
|
||||
<ChevronRightSvg />
|
||||
</Container>
|
||||
</Flex>
|
||||
</Container>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AccountNotReady, AccountState, Network, ProviderType } from './types';
|
||||
import { AccountNotReady, AccountState, AffiliateInfo, Network, ProviderType } from './types';
|
||||
|
||||
// TODO(dave4506) until we have /prices endpoint ready, we will use this whitelist for bridge order liquidity assets
|
||||
export const SUPPORTED_TOKEN_ASSET_DATA_WITH_BRIDGE_ORDERS = [
|
||||
@ -91,5 +91,13 @@ export const PROVIDER_TYPE_TO_NAME: { [key in ProviderType]: string } = {
|
||||
[ProviderType.Parity]: 'Parity',
|
||||
[ProviderType.TrustWallet]: 'Trust Wallet',
|
||||
[ProviderType.Opera]: 'Opera Wallet',
|
||||
[ProviderType.Fortmatic]: 'Fortmatic',
|
||||
[ProviderType.Fallback]: 'Fallback',
|
||||
};
|
||||
export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
export const DEFAULT_AFFILIATE_INFO: AffiliateInfo = {
|
||||
feeRecipient: NULL_ADDRESS,
|
||||
feePercentage: 0,
|
||||
};
|
||||
|
||||
export const FORTMATIC_API_KEY = process.env.INSTANT_FORTMATIC_API_KEY;
|
||||
|
@ -11,9 +11,18 @@ import {
|
||||
import { Action, actions } from '../redux/actions';
|
||||
import { asyncData } from '../redux/async_data';
|
||||
import { State } from '../redux/reducer';
|
||||
import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent, WalletSuggestion } from '../types';
|
||||
import {
|
||||
Network,
|
||||
Omit,
|
||||
OperatingSystem,
|
||||
ProviderState,
|
||||
ProviderType,
|
||||
StandardSlidingPanelContent,
|
||||
WalletSuggestion,
|
||||
} from '../types';
|
||||
import { analytics } from '../util/analytics';
|
||||
import { envUtil } from '../util/env';
|
||||
import { providerStateFactory } from '../util/provider_state_factory';
|
||||
|
||||
export interface ConnectedAccountPaymentMethodProps {}
|
||||
|
||||
@ -25,7 +34,7 @@ interface ConnectedState {
|
||||
|
||||
interface ConnectedDispatch {
|
||||
openInstallWalletPanel: () => void;
|
||||
unlockWalletAndDispatchToStore: (providerState: ProviderState) => void;
|
||||
unlockWalletAndDispatchToStore: (providerState: ProviderState, providerType: ProviderType) => void;
|
||||
}
|
||||
|
||||
type ConnectedProps = Omit<PaymentMethodProps, keyof ConnectedAccountPaymentMethodProps>;
|
||||
@ -43,10 +52,17 @@ const mapDispatchToProps = (
|
||||
ownProps: ConnectedAccountPaymentMethodProps,
|
||||
): ConnectedDispatch => ({
|
||||
openInstallWalletPanel: () => dispatch(actions.openStandardSlidingPanel(StandardSlidingPanelContent.InstallWallet)),
|
||||
unlockWalletAndDispatchToStore: (providerState: ProviderState) => {
|
||||
unlockWalletAndDispatchToStore: (providerState: ProviderState, providerType: ProviderType) => {
|
||||
const newProviderState: ProviderState = providerStateFactory.getProviderStateBasedOnProviderType(
|
||||
providerState,
|
||||
providerType,
|
||||
);
|
||||
// Updates provider state
|
||||
dispatch(actions.setProviderState(newProviderState));
|
||||
// Unlocks wallet
|
||||
analytics.trackAccountUnlockRequested();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
asyncData.fetchAccountInfoAndDispatchToStore(providerState, dispatch, true);
|
||||
asyncData.fetchAccountInfoAndDispatchToStore(newProviderState, dispatch, true);
|
||||
},
|
||||
});
|
||||
|
||||
@ -59,7 +75,8 @@ const mergeProps = (
|
||||
network: connectedState.network,
|
||||
account: connectedState.providerState.account,
|
||||
walletDisplayName: connectedState.providerState.displayName,
|
||||
onUnlockWalletClick: () => connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState),
|
||||
onUnlockWalletClick: (providerType: ProviderType) =>
|
||||
connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState, providerType),
|
||||
onInstallWalletClick: () => {
|
||||
const isMobile = envUtil.isMobileOperatingSystem();
|
||||
const walletSuggestion: WalletSuggestion = isMobile
|
||||
|
@ -22,6 +22,7 @@ const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProp
|
||||
isLoading: state.quoteRequestState === AsyncProcessState.Pending,
|
||||
assetName: assetUtils.bestNameForAsset(state.selectedAsset),
|
||||
baseCurrency: state.baseCurrency,
|
||||
account: state.providerState.account,
|
||||
});
|
||||
|
||||
interface ConnectedDispatch extends Pick<OrderDetailsProps, DispatchProperties> {}
|
||||
|
1
packages/instant/src/globals.d.ts
vendored
1
packages/instant/src/globals.d.ts
vendored
@ -10,3 +10,4 @@ declare module '*.json' {
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
||||
declare module 'fortmatic';
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { MarketBuySwapQuote } from '@0x/asset-swapper';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ActionsUnion, AddressAndEthBalanceInWei, Asset, BaseCurrency, StandardSlidingPanelContent } from '../types';
|
||||
import {
|
||||
ActionsUnion,
|
||||
AddressAndEthBalanceInWei,
|
||||
Asset,
|
||||
BaseCurrency,
|
||||
ProviderState,
|
||||
StandardSlidingPanelContent,
|
||||
} from '../types';
|
||||
|
||||
export interface PlainAction<T extends string> {
|
||||
type: T;
|
||||
@ -23,6 +30,7 @@ export enum ActionTypes {
|
||||
SetAccountStateLoading = 'SET_ACCOUNT_STATE_LOADING',
|
||||
SetAccountStateLocked = 'SET_ACCOUNT_STATE_LOCKED',
|
||||
SetAccountStateReady = 'SET_ACCOUNT_STATE_READY',
|
||||
SetAccountStateNone = 'SET_ACCOUNT_STATE_NONE',
|
||||
UpdateAccountEthBalance = 'UPDATE_ACCOUNT_ETH_BALANCE',
|
||||
UpdateEthUsdPrice = 'UPDATE_ETH_USD_PRICE',
|
||||
UpdateSelectedAssetUnitAmount = 'UPDATE_SELECTED_ASSET_UNIT_AMOUNT',
|
||||
@ -43,11 +51,13 @@ export enum ActionTypes {
|
||||
OpenStandardSlidingPanel = 'OPEN_STANDARD_SLIDING_PANEL',
|
||||
CloseStandardSlidingPanel = 'CLOSE_STANDARD_SLIDING_PANEL',
|
||||
UpdateBaseCurrency = 'UPDATE_BASE_CURRENCY',
|
||||
SetProviderState = 'SET_PROVIDER_STATE',
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
setAccountStateLoading: () => createAction(ActionTypes.SetAccountStateLoading),
|
||||
setAccountStateLocked: () => createAction(ActionTypes.SetAccountStateLocked),
|
||||
setAccountStateNone: () => createAction(ActionTypes.SetAccountStateNone),
|
||||
setAccountStateReady: (address: string) => createAction(ActionTypes.SetAccountStateReady, address),
|
||||
updateAccountEthBalance: (addressAndBalance: AddressAndEthBalanceInWei) =>
|
||||
createAction(ActionTypes.UpdateAccountEthBalance, addressAndBalance),
|
||||
@ -73,4 +83,5 @@ export const actions = {
|
||||
createAction(ActionTypes.OpenStandardSlidingPanel, content),
|
||||
closeStandardSlidingPanel: () => createAction(ActionTypes.CloseStandardSlidingPanel),
|
||||
updateBaseCurrency: (baseCurrency: BaseCurrency) => createAction(ActionTypes.UpdateBaseCurrency, baseCurrency),
|
||||
setProviderState: (providerState: ProviderState) => createAction(ActionTypes.SetProviderState, providerState),
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import { assetUtils } from '../util/asset';
|
||||
import { coinbaseApi } from '../util/coinbase_api';
|
||||
import { errorFlasher } from '../util/error_flasher';
|
||||
import { errorReporter } from '../util/error_reporter';
|
||||
import { providerStateFactory } from '../util/provider_state_factory';
|
||||
import { swapQuoteUpdater } from '../util/swap_quote_updater';
|
||||
|
||||
import { actions } from './actions';
|
||||
@ -59,24 +60,39 @@ export const asyncData = {
|
||||
providerState: ProviderState,
|
||||
dispatch: Dispatch,
|
||||
shouldAttemptUnlock: boolean = false,
|
||||
shouldSetToLoading: boolean = false,
|
||||
) => {
|
||||
const web3Wrapper = providerState.web3Wrapper;
|
||||
const provider = providerState.provider;
|
||||
if (shouldSetToLoading && providerState.account.state !== AccountState.Loading) {
|
||||
let availableAddresses: string[] = [];
|
||||
if (shouldAttemptUnlock && providerState.account.state !== AccountState.Loading) {
|
||||
dispatch(actions.setAccountStateLoading());
|
||||
}
|
||||
let availableAddresses: string[];
|
||||
try {
|
||||
// HACK: Fortmatic's getAvailableAddressesAsync behaves in ways that default wallet behavior can't handle
|
||||
if ((provider as any).isFortmatic) {
|
||||
availableAddresses =
|
||||
(provider as any).isLoggedIn || shouldAttemptUnlock
|
||||
? await web3Wrapper.getAvailableAddressesAsync()
|
||||
: [];
|
||||
} else {
|
||||
// TODO(bmillman): Add support at the web3Wrapper level for calling `eth_requestAccounts` instead of calling enable here
|
||||
const isPrivacyModeEnabled = (provider as any).enable !== undefined;
|
||||
availableAddresses =
|
||||
isPrivacyModeEnabled && shouldAttemptUnlock
|
||||
? await (provider as any).enable()
|
||||
: await web3Wrapper.getAvailableAddressesAsync();
|
||||
}
|
||||
} catch (e) {
|
||||
analytics.trackAccountUnlockDenied();
|
||||
if (e.message.includes('Fortmatic: User denied account access.')) {
|
||||
// If Fortmatic is not used, revert to injected provider
|
||||
const initialProviderState = providerStateFactory.getInitialProviderStateWithCurrentProviderState(
|
||||
providerState,
|
||||
);
|
||||
dispatch(actions.setProviderState(initialProviderState));
|
||||
} else {
|
||||
dispatch(actions.setAccountStateLocked());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!_.isEmpty(availableAddresses)) {
|
||||
@ -84,7 +100,7 @@ export const asyncData = {
|
||||
dispatch(actions.setAccountStateReady(activeAddress));
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
asyncData.fetchAccountBalanceAndDispatchToStore(activeAddress, providerState.web3Wrapper, dispatch);
|
||||
} else {
|
||||
} else if (providerState.account.state !== AccountState.Loading) {
|
||||
dispatch(actions.setAccountStateLocked());
|
||||
}
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { LOADING_ACCOUNT, LOCKED_ACCOUNT } from '../constants';
|
||||
import { LOADING_ACCOUNT, LOCKED_ACCOUNT, NO_ACCOUNT } from '../constants';
|
||||
import { assetMetaDataMap } from '../data/asset_meta_data_map';
|
||||
import {
|
||||
Account,
|
||||
@ -77,6 +77,8 @@ export const createReducer = (initialState: State) => {
|
||||
return reduceStateWithAccount(state, LOADING_ACCOUNT);
|
||||
case ActionTypes.SetAccountStateLocked:
|
||||
return reduceStateWithAccount(state, LOCKED_ACCOUNT);
|
||||
case ActionTypes.SetAccountStateNone:
|
||||
return reduceStateWithAccount(state, NO_ACCOUNT);
|
||||
case ActionTypes.SetAccountStateReady: {
|
||||
const address = action.data;
|
||||
let newAccount: AccountReady = {
|
||||
@ -252,6 +254,11 @@ export const createReducer = (initialState: State) => {
|
||||
...state,
|
||||
baseCurrency: action.data,
|
||||
};
|
||||
case ActionTypes.SetProviderState:
|
||||
return {
|
||||
...state,
|
||||
providerState: action.data,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@ -291,7 +298,6 @@ const doesSwapQuoteMatchState = (swapQuote: MarketBuySwapQuote, state: State): b
|
||||
selectedAssetMetaData.decimals,
|
||||
);
|
||||
const doesAssetAmountMatch = selectedAssetAmountBaseUnits.eq(swapQuote.makerAssetFillAmount);
|
||||
|
||||
return doesAssetAmountMatch;
|
||||
} else {
|
||||
return true;
|
||||
|
@ -27,6 +27,7 @@ export enum ColorOption {
|
||||
red = 'red',
|
||||
darkBlue = 'darkBlue',
|
||||
lightBlue = 'lightBlue',
|
||||
whiteBackground = 'whiteBackground',
|
||||
}
|
||||
|
||||
export const theme: Theme = {
|
||||
@ -44,6 +45,7 @@ export const theme: Theme = {
|
||||
red: '#D00000',
|
||||
darkBlue: '#135df6',
|
||||
lightBlue: '#F2F7FF',
|
||||
whiteBackground: '#FFFFFF',
|
||||
};
|
||||
|
||||
export const transparentWhite = 'rgba(255,255,255,0.3)';
|
||||
|
@ -114,10 +114,12 @@ export interface ProviderState {
|
||||
swapQuoteConsumer: SwapQuoteConsumer;
|
||||
web3Wrapper: Web3Wrapper;
|
||||
account: Account;
|
||||
orderSource: OrderSource;
|
||||
isProviderInjected: boolean;
|
||||
}
|
||||
|
||||
export enum AccountState {
|
||||
None = 'NONE,',
|
||||
None = 'NONE',
|
||||
Loading = 'LOADING',
|
||||
Ready = 'READY',
|
||||
Locked = 'LOCKED',
|
||||
@ -185,6 +187,7 @@ export enum ProviderType {
|
||||
Cipher = 'CIPHER',
|
||||
TrustWallet = 'TRUST_WALLET',
|
||||
Opera = 'OPERA',
|
||||
Fortmatic = 'Fortmatic',
|
||||
Fallback = 'FALLBACK',
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,8 @@ export const envUtil = {
|
||||
return ProviderType.Parity;
|
||||
} else if (anyProvider.isMetaMask) {
|
||||
return ProviderType.MetaMask;
|
||||
} else if (anyProvider.isFortmatic) {
|
||||
return ProviderType.Fortmatic;
|
||||
} else if (_.get(window, 'SOFA') !== undefined) {
|
||||
return ProviderType.CoinbaseWallet;
|
||||
} else if (_.get(window, '__CIPHER__') !== undefined) {
|
||||
@ -58,7 +60,7 @@ export const envUtil = {
|
||||
} else if (envUtil.getBrowser() === Browser.Opera && !anyProvider.isMetaMask) {
|
||||
return ProviderType.Opera;
|
||||
}
|
||||
return;
|
||||
return undefined;
|
||||
},
|
||||
getProviderName(provider: ZeroExProvider): string {
|
||||
const providerTypeIfExists = envUtil.getProviderType(provider);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { providerUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
import * as Fortmatic from 'fortmatic';
|
||||
|
||||
import { LOADING_ACCOUNT, NO_ACCOUNT } from '../constants';
|
||||
import { Maybe, Network, OrderSource, ProviderState } from '../types';
|
||||
import { FORTMATIC_API_KEY, LOCKED_ACCOUNT, NO_ACCOUNT } from '../constants';
|
||||
import { Maybe, Network, OrderSource, ProviderState, ProviderType } from '../types';
|
||||
import { envUtil } from '../util/env';
|
||||
|
||||
import { assetSwapperFactory } from './asset_swapper_factory';
|
||||
@ -50,7 +50,9 @@ export const providerStateFactory = {
|
||||
web3Wrapper: new Web3Wrapper(provider),
|
||||
swapQuoter: assetSwapperFactory.getSwapQuoter(provider, orderSource, network),
|
||||
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(provider, network),
|
||||
account: LOADING_ACCOUNT,
|
||||
account: LOCKED_ACCOUNT,
|
||||
orderSource,
|
||||
isProviderInjected: false,
|
||||
};
|
||||
return providerState;
|
||||
},
|
||||
@ -68,7 +70,9 @@ export const providerStateFactory = {
|
||||
web3Wrapper: new Web3Wrapper(injectedProviderIfExists),
|
||||
swapQuoter: assetSwapperFactory.getSwapQuoter(injectedProviderIfExists, orderSource, network),
|
||||
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(injectedProviderIfExists, network),
|
||||
account: LOADING_ACCOUNT,
|
||||
account: LOCKED_ACCOUNT,
|
||||
orderSource,
|
||||
isProviderInjected: true,
|
||||
};
|
||||
return providerState;
|
||||
} else {
|
||||
@ -89,7 +93,66 @@ export const providerStateFactory = {
|
||||
swapQuoter: assetSwapperFactory.getSwapQuoter(provider, orderSource, network),
|
||||
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(provider, network),
|
||||
account: NO_ACCOUNT,
|
||||
orderSource,
|
||||
isProviderInjected: true,
|
||||
};
|
||||
return providerState;
|
||||
},
|
||||
// function to call getInitialProviderState with parameters retreived from a provided ProviderState
|
||||
getInitialProviderStateWithCurrentProviderState: (currentProviderState: ProviderState): ProviderState => {
|
||||
const orderSource = currentProviderState.orderSource;
|
||||
const chainId = currentProviderState.swapQuoter.chainId;
|
||||
// If provider is provided to instant, use that and the displayName
|
||||
if (!currentProviderState.isProviderInjected) {
|
||||
return providerStateFactory.getInitialProviderState(
|
||||
orderSource,
|
||||
chainId,
|
||||
currentProviderState.provider,
|
||||
currentProviderState.displayName,
|
||||
);
|
||||
}
|
||||
const newProviderState = providerStateFactory.getInitialProviderState(orderSource, chainId);
|
||||
newProviderState.account = LOCKED_ACCOUNT;
|
||||
return newProviderState;
|
||||
},
|
||||
getProviderStateBasedOnProviderType: (
|
||||
currentProviderState: ProviderState,
|
||||
providerType: ProviderType,
|
||||
): ProviderState => {
|
||||
const chainId = currentProviderState.swapQuoter.chainId;
|
||||
const orderSource = currentProviderState.orderSource;
|
||||
// Returns current provider if the provider type selected is not found
|
||||
if (providerType === ProviderType.MetaMask) {
|
||||
const provider = providerFactory.getInjectedProviderIfExists();
|
||||
if (provider) {
|
||||
return {
|
||||
displayName: envUtil.getProviderDisplayName(provider),
|
||||
name: envUtil.getProviderName(provider),
|
||||
provider,
|
||||
web3Wrapper: new Web3Wrapper(provider),
|
||||
swapQuoter: assetSwapperFactory.getSwapQuoter(provider, orderSource, chainId),
|
||||
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(provider, chainId),
|
||||
account: LOCKED_ACCOUNT,
|
||||
orderSource,
|
||||
isProviderInjected: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (providerType === ProviderType.Fortmatic) {
|
||||
const fm = new Fortmatic(FORTMATIC_API_KEY);
|
||||
const fmProvider = fm.getProvider();
|
||||
return {
|
||||
displayName: envUtil.getProviderDisplayName(fmProvider),
|
||||
name: envUtil.getProviderName(fmProvider),
|
||||
provider: fmProvider,
|
||||
web3Wrapper: new Web3Wrapper(fmProvider),
|
||||
swapQuoter: assetSwapperFactory.getSwapQuoter(fmProvider, orderSource, chainId),
|
||||
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(fmProvider, chainId),
|
||||
account: LOCKED_ACCOUNT,
|
||||
orderSource,
|
||||
isProviderInjected: true,
|
||||
};
|
||||
}
|
||||
return providerStateFactory.getInitialProviderState(orderSource, chainId);
|
||||
},
|
||||
};
|
||||
|
@ -94,11 +94,17 @@ const generateConfig = (dischargeTarget, heapConfigOptions, rollbarConfigOptions
|
||||
? process.env.INSTANT_INFURA_PROJECT_ID_PRODUCTION
|
||||
: process.env.INSTANT_INFURA_PROJECT_ID_DEVELOPMENT;
|
||||
|
||||
const fortmaticApiKey =
|
||||
dischargeTarget === 'production'
|
||||
? process.env.INSTANT_FORTMATIC_API_KEY_PRODUCTION
|
||||
: process.env.INSTANT_FORTMATIC_API_KEY_DEVELOPMENT;
|
||||
|
||||
const envVars = {
|
||||
GIT_SHA: JSON.stringify(GIT_SHA),
|
||||
NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version),
|
||||
ROLLBAR_ENABLED: rollbarEnabled,
|
||||
HEAP_ENABLED: heapEnabled,
|
||||
INSTANT_FORTMATIC_API_KEY: JSON.stringify(fortmaticApiKey),
|
||||
INSTANT_INFURA_PROJECT_ID: JSON.stringify(infuraProjectId),
|
||||
};
|
||||
if (dischargeTarget) {
|
||||
|
17
yarn.lock
17
yarn.lock
@ -984,6 +984,12 @@
|
||||
core-js "^2.6.5"
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@7.3.4":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.1.5", "@babel/runtime@^7.3.1":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
||||
@ -7759,6 +7765,13 @@ format-util@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz#032dca4a116262a12c43f4c3ec8566416c5b2d95"
|
||||
|
||||
fortmatic@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fortmatic/-/fortmatic-1.0.1.tgz#ecb2c6777cd25658befe5e86c5eeddcb6c4db472"
|
||||
integrity sha512-D48g0talOofK6AdwppO2VL/rRjLZb69Qf6fBR1lYUZ2rqMCM8WbBqoEDgFgJyjO1YV7XCJGbm+mW0/y2RxxbEg==
|
||||
dependencies:
|
||||
"@babel/runtime" "7.3.4"
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
@ -14102,6 +14115,10 @@ regenerator-runtime@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
|
||||
regenerator-runtime@^0.12.0:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
|
||||
regenerator-runtime@^0.13.2:
|
||||
version "0.13.2"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
|
||||
|
Loading…
x
Reference in New Issue
Block a user