WIP governance page
This commit is contained in:
parent
5676702da6
commit
1ef434f95e
@ -6,7 +6,7 @@ import { ThemeInterface } from 'ts/components/siteWrap';
|
||||
|
||||
import { colors } from 'ts/style/colors';
|
||||
|
||||
interface ButtonInterface {
|
||||
export interface ButtonInterface {
|
||||
bgColor?: string;
|
||||
borderColor?: string;
|
||||
color?: string;
|
||||
@ -22,7 +22,7 @@ interface ButtonInterface {
|
||||
type?: string;
|
||||
target?: string;
|
||||
to?: string;
|
||||
onClick?: () => any;
|
||||
onClick?: (e: Event) => any;
|
||||
theme?: ThemeInterface;
|
||||
shouldUseAnchorTag?: boolean;
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ const navData = [
|
||||
description: 'Build on the 0x protocol',
|
||||
url: WebsitePaths.LaunchKit,
|
||||
},
|
||||
{
|
||||
title: 'Governance',
|
||||
description: 'xxxxxxxxx',
|
||||
url: WebsitePaths.Governance,
|
||||
},
|
||||
];
|
||||
|
||||
export const DropdownProducts: React.FunctionComponent<{}> = () => (
|
||||
|
@ -22,6 +22,7 @@ import { NextAboutPress } from 'ts/pages/about/press';
|
||||
import { NextAboutTeam } from 'ts/pages/about/team';
|
||||
import { Credits } from 'ts/pages/credits';
|
||||
import { NextEcosystem } from 'ts/pages/ecosystem';
|
||||
import { Governance } from 'ts/pages/governance/governance';
|
||||
import { Next0xInstant } from 'ts/pages/instant';
|
||||
import { NextLanding } from 'ts/pages/landing';
|
||||
import { NextLaunchKit } from 'ts/pages/launch_kit';
|
||||
@ -116,7 +117,7 @@ render(
|
||||
<Route exact={true} path={WebsitePaths.Instant} component={Next0xInstant as any} />
|
||||
<Route exact={true} path={WebsitePaths.LaunchKit} component={NextLaunchKit as any} />
|
||||
<Route exact={true} path={WebsitePaths.Ecosystem} component={NextEcosystem as any} />
|
||||
<Route exact={true} path={WebsitePaths.Vote} component={VotePlaceholder as any} />
|
||||
<Route exact={true} path={WebsitePaths.Vote} component={Governance as any} />
|
||||
<Route
|
||||
exact={true}
|
||||
path={WebsitePaths.AboutMission}
|
||||
|
45
packages/website/ts/pages/governance/countdown.tsx
Normal file
45
packages/website/ts/pages/governance/countdown.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { colors } from 'ts/style/colors';
|
||||
|
||||
import { Heading, Paragraph } from 'ts/components/text';
|
||||
|
||||
interface Props {
|
||||
deadline: number;
|
||||
}
|
||||
|
||||
const now = moment();
|
||||
|
||||
export const Countdown: React.StatelessComponent<Props> = ({ deadline }) => {
|
||||
const time = moment(deadline, 'X');
|
||||
const isPassed = time.isBefore(now);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', marginBottom: '10px' }}>
|
||||
<Paragraph style={{ marginRight: '10px' }}>Time left to vote</Paragraph>
|
||||
<Paragraph color="#AE5300">{isPassed ? 'Voting has ended' : getRelativeTime(time)}</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const VoteDeadline: React.StatelessComponent<Props> = ({ deadline }) => {
|
||||
const time = moment(deadline, 'X');
|
||||
const isPassed = time.isBefore(now);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', marginBottom: '10px' }}>
|
||||
<Paragraph style={{ marginRight: '10px' }}>Vote {isPassed ? 'ended' : 'ends'}: {time.format('YYYY/MM/DD h:mm:ss Z')}</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getRelativeTime(deadline: moment.Moment): string {
|
||||
return deadline.fromNow(false);
|
||||
}
|
377
packages/website/ts/pages/governance/governance.tsx
Normal file
377
packages/website/ts/pages/governance/governance.tsx
Normal file
@ -0,0 +1,377 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Banner } from 'ts/components/banner';
|
||||
import { Button } from 'ts/components/button';
|
||||
import { Column, FlexWrap, Section, WrapSticky } from 'ts/components/newLayout';
|
||||
import { SiteWrap } from 'ts/components/siteWrap';
|
||||
import { Heading, Paragraph } from 'ts/components/text';
|
||||
import { Countdown, VoteDeadline } from 'ts/pages/governance/countdown';
|
||||
|
||||
import { ModalContact } from 'ts/components/modals/modal_contact';
|
||||
import { ModalVote } from 'ts/pages/governance/modal_vote';
|
||||
import { colors } from 'ts/style/colors';
|
||||
|
||||
interface LabelInterface {
|
||||
[key: number]: string;
|
||||
}
|
||||
|
||||
interface RatingBarProps {
|
||||
rating: number;
|
||||
color?: string;
|
||||
labels: LabelInterface;
|
||||
}
|
||||
|
||||
interface RatingBulletProps {
|
||||
color: string;
|
||||
isFilled: boolean;
|
||||
}
|
||||
|
||||
interface VoteBarProps {
|
||||
label: string;
|
||||
color: string;
|
||||
totalVotes: number;
|
||||
votes: number;
|
||||
}
|
||||
|
||||
interface VoteColumnProps {
|
||||
color: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface ConnectedWalletMarkProps {
|
||||
isConnected: boolean;
|
||||
providerName?: string;
|
||||
}
|
||||
|
||||
const benefitLabels: LabelInterface = {
|
||||
1: 'little benefit',
|
||||
2: 'little benefit',
|
||||
3: 'little benefit',
|
||||
4: 'high benefit',
|
||||
5: 'extreme benefit',
|
||||
};
|
||||
|
||||
const riskLabels: LabelInterface = {
|
||||
1: 'little risk',
|
||||
2: 'little risk',
|
||||
3: 'little risk',
|
||||
4: 'high risk',
|
||||
5: 'extreme risk',
|
||||
};
|
||||
|
||||
const complexityLabels: LabelInterface = {
|
||||
1: 'simple',
|
||||
2: 'simple',
|
||||
3: 'simple',
|
||||
4: 'very complex',
|
||||
5: 'extremely complex',
|
||||
};
|
||||
|
||||
const proposalData = {
|
||||
title: 'MultiAssetProxy Approval',
|
||||
summary: `The world's assets are becoming tokenized on public blockchains. 0x Protocol is free, open-source infrastracture that developers and businesses utilize to build products that enable the purchasing and trading of crypto tokens.`,
|
||||
url: '#',
|
||||
votingDeadline: 1609459200,
|
||||
results: {
|
||||
total: 2887000,
|
||||
votes: [
|
||||
{
|
||||
label: 'Yes',
|
||||
total: 2165250,
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
total: 721750,
|
||||
},
|
||||
],
|
||||
},
|
||||
benefit: {
|
||||
title: 'Ecosystem Benefit',
|
||||
summary: `The world's assets are becoming tokenized on public blockchains. 0x Protocol is free, open-source infrastracture that developers and businesses utilize to build products that enable the purchasing and trading of crypto tokens.`,
|
||||
rating: 4,
|
||||
links: [
|
||||
{
|
||||
text: 'Learn More',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
stakes: {
|
||||
title: 'Stakes',
|
||||
summary: `The MultiAssetProxy allows for smart contracts to take control of a user’s funds. If there is a bug in the contract, it will allow potential attackers to gain control of large amounts of assets. We rate this as high risk, and despite our extensive internal and external audits, we encourage as many people as possible to check our code. `,
|
||||
rating: 4,
|
||||
links: [
|
||||
{
|
||||
text: 'View Audit',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
text: 'View Code',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
complexity: {
|
||||
title: 'Complexity of Code',
|
||||
summary: `The world's assets are becoming tokenized on public blockchains. 0x Protocol is free, open-source infrastracture that developers and businesses utilize to build products that enable the purchasing and trading of crypto tokens.`,
|
||||
rating: 1,
|
||||
links: [
|
||||
{
|
||||
text: 'View Code',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const RatingBar: React.StatelessComponent<RatingBarProps> = ({ rating, color, labels }) => {
|
||||
const id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const ratingLabel = labels[rating];
|
||||
const ratingPlaceholders = Array.from(new Array(5), (value, index) => index + 1);
|
||||
const fillCheck = (currentIndex: number) => currentIndex <= rating;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', marginBottom: '12px' }}>
|
||||
{ratingPlaceholders.map((currentIndex: number) => <RatingBullet color={color} key={`${id}-${currentIndex}`} isFilled={fillCheck(currentIndex)} />)}
|
||||
</div>
|
||||
<Paragraph>{ratingLabel}</Paragraph>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VoteColumn = styled.div<VoteColumnProps>`
|
||||
background-color: ${props => props.color};
|
||||
width: calc(${props => props.width}% - 45px);
|
||||
height: 13px;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
const VoteColumnPrefix = styled.span`
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
width: 40px;
|
||||
margin-right: 5px;
|
||||
font-weight: 300;
|
||||
`;
|
||||
|
||||
const VoteColumnLabel = styled.span`
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
font-weight: 300;
|
||||
`;
|
||||
|
||||
const VoteBar: React.StatelessComponent<VoteBarProps> = ({ totalVotes, votes, color, label }) => {
|
||||
const percentage = (100 / totalVotes) * votes;
|
||||
const percentageLabel = `${percentage}%`;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '12px', marginLeft: '-50px' }}>
|
||||
<VoteColumnPrefix>{label}</VoteColumnPrefix>
|
||||
<div style={{ display: 'flex', flex: 1, alignItems: 'center' }}>
|
||||
<VoteColumn color={color} width={percentage} />
|
||||
<VoteColumnLabel>{percentageLabel}</VoteColumnLabel>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectedWalletMark: React.StatelessComponent<ConnectedWalletMarkProps> = ({ isConnected, providerName }) => {
|
||||
const typeLabel = isConnected ? providerName || 'Wallet connected' : 'Connect your wallet';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
const Status = styled.span<ConnectedWalletMarkProps>`
|
||||
background: ${props => props.isConnected ? colors.brandLight : '#FF2828'};
|
||||
border-radius: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
`;
|
||||
|
||||
const Label = styled.span`
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Status isConnected={isConnected} />
|
||||
<Label>{typeLabel}</Label>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export class Governance extends React.Component {
|
||||
public state = {
|
||||
isContactModalOpen: false,
|
||||
isVoteModalOpen: false,
|
||||
isWalletConnected: false,
|
||||
isVoteReceived: false,
|
||||
providerName: 'Metamask',
|
||||
};
|
||||
public render(): React.ReactNode {
|
||||
const { isVoteReceived, isWalletConnected, providerName } = this.state;
|
||||
const buildAction = (
|
||||
<Button href="/docs" isWithArrow={true} isAccentColor={true} isTransparent={true} borderColor={colors.brandLight}>
|
||||
Build on 0x
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<SiteWrap theme="dark">
|
||||
<DocumentTitle title="Features & Benefits - 0x" />
|
||||
<Section maxWidth="1170px" isFlex={true}>
|
||||
<Column width="55%" maxWidth="560px">
|
||||
<Countdown deadline={proposalData.votingDeadline} />
|
||||
<Heading size="medium">
|
||||
{proposalData.title}
|
||||
</Heading>
|
||||
<Paragraph>
|
||||
{proposalData.summary}
|
||||
</Paragraph>
|
||||
<Button
|
||||
href={proposalData.url}
|
||||
target={!_.isUndefined(proposalData.url) ? '_blank' : undefined}
|
||||
isWithArrow={true}
|
||||
isAccentColor={true}
|
||||
>
|
||||
Learn More
|
||||
</Button>
|
||||
</Column>
|
||||
<Column width="30%" maxWidth="300px">
|
||||
<VoteDeadline deadline={proposalData.votingDeadline} />
|
||||
<ConnectedWalletMark isConnected={isWalletConnected} providerName={providerName} />
|
||||
<VoteButton onClick={this._onOpenVoteModal.bind(this)} isWithArrow={false} isAccentColor={true} isTransparent={true} borderColor={colors.brandLight}>
|
||||
{isVoteReceived ? 'Vote Received' : 'Vote'}
|
||||
</VoteButton>
|
||||
<Paragraph marginBottom="10px">Results (2,887,000 ZRX total vote)</Paragraph>
|
||||
<VoteBar label="Yes" color={colors.brandLight} totalVotes={2887000} votes={2165250} />
|
||||
<VoteBar label="No" color="#AE5400" totalVotes={2887000} votes={721750} />
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Section bgColor="dark" maxWidth="1170px">
|
||||
<SectionWrap>
|
||||
<Heading>
|
||||
{proposalData.benefit.title}
|
||||
</Heading>
|
||||
<FlexWrap>
|
||||
<Column width="55%" maxWidth="560px">
|
||||
<Paragraph>{proposalData.benefit.summary}</Paragraph>
|
||||
<Button
|
||||
href={proposalData.url}
|
||||
target={!_.isUndefined(proposalData.url) ? '_blank' : undefined}
|
||||
isWithArrow={true}
|
||||
isAccentColor={true}
|
||||
>
|
||||
Audit Code
|
||||
</Button>
|
||||
</Column>
|
||||
<Column width="30%" maxWidth="360px">
|
||||
<RatingBar color={colors.brandLight} labels={benefitLabels} rating={proposalData.benefit.rating} />
|
||||
</Column>
|
||||
</FlexWrap>
|
||||
</SectionWrap>
|
||||
<SectionWrap>
|
||||
<Heading>
|
||||
{proposalData.stakes.title}
|
||||
</Heading>
|
||||
<FlexWrap>
|
||||
<Column width="55%" maxWidth="560px">
|
||||
<Paragraph>{proposalData.stakes.summary}</Paragraph>
|
||||
</Column>
|
||||
<Column width="30%" maxWidth="360px">
|
||||
<RatingBar color="#AE5400" labels={riskLabels} rating={proposalData.stakes.rating} />
|
||||
</Column>
|
||||
</FlexWrap>
|
||||
</SectionWrap>
|
||||
<SectionWrap>
|
||||
<Heading>
|
||||
{proposalData.complexity.title}
|
||||
</Heading>
|
||||
<FlexWrap>
|
||||
<Column width="55%" maxWidth="560px">
|
||||
<Paragraph>{proposalData.complexity.summary}</Paragraph>
|
||||
</Column>
|
||||
<Column width="30%" maxWidth="360px">
|
||||
<RatingBar labels={complexityLabels} rating={proposalData.complexity.rating} />
|
||||
</Column>
|
||||
</FlexWrap>
|
||||
</SectionWrap>
|
||||
</Section>
|
||||
|
||||
<Banner
|
||||
heading="Ready to get started?"
|
||||
subline="Dive into our docs, or contact us if needed"
|
||||
mainCta={{ text: 'Get Started', href: '/docs' }}
|
||||
secondaryCta={{ text: 'Get in Touch', onClick: this._onOpenContactModal.bind(this) }}
|
||||
/>
|
||||
<ModalContact isOpen={this.state.isContactModalOpen} onDismiss={this._onDismissContactModal} />
|
||||
<ModalVote isOpen={this.state.isVoteModalOpen} onDismiss={this._onDismissVoteModal} onWalletConnected={this._onWalletConnected.bind(this)} onVoted={this._onVoteReceived.bind(this)} />
|
||||
</SiteWrap>
|
||||
);
|
||||
}
|
||||
|
||||
public _onOpenContactModal = (): void => {
|
||||
this.setState({ ...this.state, isContactModalOpen: true });
|
||||
};
|
||||
|
||||
public _onDismissContactModal = (): void => {
|
||||
this.setState({ ...this.state, isContactModalOpen: false });
|
||||
};
|
||||
|
||||
public _onOpenVoteModal = (): void => {
|
||||
this.setState({ ...this.state, isVoteModalOpen: true });
|
||||
};
|
||||
|
||||
public _onDismissVoteModal = (): void => {
|
||||
this.setState({ ...this.state, isVoteModalOpen: false });
|
||||
};
|
||||
|
||||
public _onWalletConnected = (providerName: string): void => {
|
||||
this.setState({ ...this.state, isWalletConnected: true, providerName });
|
||||
};
|
||||
|
||||
public _onVoteReceived = (): void => {
|
||||
this.setState({ ...this.state, isVoteReceived: true });
|
||||
};
|
||||
}
|
||||
|
||||
interface SectionProps {
|
||||
isNotRelative?: boolean;
|
||||
}
|
||||
|
||||
const SectionWrap = styled.div`
|
||||
& + & {
|
||||
padding-top: 50px;
|
||||
}
|
||||
`;
|
||||
|
||||
const VoteButton = styled(Button)`
|
||||
display: block;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const RatingBullet = styled.div<RatingBulletProps>`
|
||||
background-color: ${props => props.isFilled && props.color};
|
||||
border: 1px solid ${props => props.color};
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
& + & {
|
||||
margin-left: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
RatingBullet.defaultProps = {
|
||||
color: colors.white,
|
||||
};
|
793
packages/website/ts/pages/governance/modal_vote.tsx
Normal file
793
packages/website/ts/pages/governance/modal_vote.tsx
Normal file
@ -0,0 +1,793 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { colors } from 'ts/style/colors';
|
||||
|
||||
import { DialogContent, DialogOverlay } from '@reach/dialog';
|
||||
import '@reach/dialog/styles.css';
|
||||
|
||||
// import { LedgerSubprovider, Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses';
|
||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
||||
import {
|
||||
BigNumber,
|
||||
signTypedDataUtils,
|
||||
} from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
|
||||
import { Button } from 'ts/components/button';
|
||||
import { Icon } from 'ts/components/icon';
|
||||
import { Input, InputWidth } from 'ts/components/modals/input';
|
||||
import { Heading, Paragraph } from 'ts/components/text';
|
||||
import { GlobalStyle } from 'ts/constants/globalStyle';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
import {
|
||||
signatureUtils,
|
||||
} from '0x.js';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import {
|
||||
ECSignature,
|
||||
} from '@0x/types';
|
||||
|
||||
import {
|
||||
ledgerEthereumBrowserClientFactoryAsync,
|
||||
LedgerSubprovider,
|
||||
MetamaskSubprovider,
|
||||
RedundantSubprovider,
|
||||
RPCSubprovider,
|
||||
SignerSubprovider,
|
||||
Web3ProviderEngine,
|
||||
} from '@0x/subproviders';
|
||||
import { BlockParam, LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import {
|
||||
BlockchainCallErrs,
|
||||
BlockchainErrs,
|
||||
ContractInstance,
|
||||
Fill,
|
||||
InjectedProvider,
|
||||
InjectedProviderObservable,
|
||||
InjectedProviderUpdate,
|
||||
JSONRPCPayload,
|
||||
Providers,
|
||||
ProviderType,
|
||||
Side,
|
||||
SideToAssetToken,
|
||||
Token,
|
||||
TokenByAddress,
|
||||
} from 'ts/types';
|
||||
import { configs } from 'ts/utils/configs';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import FilterSubprovider from 'web3-provider-engine/subproviders/filters';
|
||||
|
||||
const providerToName: { [provider: string]: string } = {
|
||||
[Providers.Metamask]: constants.PROVIDER_NAME_METAMASK,
|
||||
[Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER,
|
||||
[Providers.Mist]: constants.PROVIDER_NAME_MIST,
|
||||
[Providers.CoinbaseWallet]: constants.PROVIDER_NAME_COINBASE_WALLET,
|
||||
[Providers.Cipher]: constants.PROVIDER_NAME_CIPHER,
|
||||
};
|
||||
|
||||
export enum ModalContactType {
|
||||
General = 'GENERAL',
|
||||
MarketMaker = 'MARKET_MAKER',
|
||||
}
|
||||
|
||||
export enum VoteValue {
|
||||
Yes = 'Yes',
|
||||
No = 'No',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
theme?: GlobalStyle;
|
||||
isOpen?: boolean;
|
||||
onDismiss?: () => void;
|
||||
onWalletConnected?: (providerName: string) => void;
|
||||
onVoted?: () => void;
|
||||
modalContactType: ModalContactType;
|
||||
}
|
||||
|
||||
interface State {
|
||||
currentBalance: string;
|
||||
isWalletConnected: boolean;
|
||||
providerName: string;
|
||||
isSubmitting: boolean;
|
||||
isSuccessful: boolean;
|
||||
isVoted: boolean;
|
||||
votePreference: string | null;
|
||||
errors: ErrorProps;
|
||||
}
|
||||
|
||||
interface FormProps {
|
||||
isSuccessful?: boolean;
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
interface ErrorResponseProps {
|
||||
param: string;
|
||||
location: string;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
errors: ErrorResponseProps[];
|
||||
}
|
||||
|
||||
interface ErrorProps {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export enum SignatureType {
|
||||
Illegal, // unused 0
|
||||
Invalid, // unused 1
|
||||
EIP712, // 2
|
||||
EthSign, // 3
|
||||
Wallet, // unused 4
|
||||
Validator, // unused 5
|
||||
PreSigned, // unused 6
|
||||
NSignatureTypes, // unused 7
|
||||
}
|
||||
|
||||
export class ModalVote extends React.Component<Props> {
|
||||
public static defaultProps = {
|
||||
modalContactType: ModalContactType.General,
|
||||
};
|
||||
public networkId: number;
|
||||
public state: State = {
|
||||
currentBalance: '0.00',
|
||||
isWalletConnected: false,
|
||||
providerName: 'Metamask',
|
||||
isSubmitting: false,
|
||||
isSuccessful: false,
|
||||
isVoted: false,
|
||||
votePreference: null,
|
||||
errors: {},
|
||||
};
|
||||
// shared fields
|
||||
public nameRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
public emailRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
public companyProjectRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
public commentsRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
// general lead fields
|
||||
public linkRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
// market maker lead fields
|
||||
public countryRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
public fundSizeRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
// blockchain related
|
||||
private _injectedProviderIfExists?: InjectedProvider;
|
||||
private _web3Wrapper?: Web3Wrapper;
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
public render(): React.ReactNode {
|
||||
const { isOpen, onDismiss } = this.props;
|
||||
const { isSuccessful, errors, votePreference } = this.state;
|
||||
return (
|
||||
<>
|
||||
<DialogOverlay
|
||||
style={{ background: 'rgba(0, 0, 0, 0.75)', zIndex: 30 }}
|
||||
isOpen={isOpen}
|
||||
onDismiss={onDismiss}
|
||||
>
|
||||
<StyledDialogContent>
|
||||
<Form onSubmit={this._onSubmitAsync.bind(this)} isSuccessful={isSuccessful}>
|
||||
{this._renderFormContent(errors)}
|
||||
<ButtonRow>
|
||||
<Button
|
||||
color="#5C5C5C"
|
||||
isNoBorder={true}
|
||||
isTransparent={true}
|
||||
type="button"
|
||||
onClick={this.props.onDismiss}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
{this.state.isWalletConnected && <ButtonDisabled disabled={!votePreference}>Submit</ButtonDisabled>}
|
||||
</ButtonRow>
|
||||
</Form>
|
||||
<Confirmation isSuccessful={isSuccessful}>
|
||||
<Icon name="rocketship" size="large" margin={[0, 0, 'default', 0]} />
|
||||
<Heading color={colors.textDarkPrimary} size={34} asElement="h2">
|
||||
Vote Received
|
||||
</Heading>
|
||||
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
|
||||
Your vote will help to decide the future of the protocol. You will be receiving a custom “I voted” NFT as a token of our appreciation.
|
||||
</Paragraph>
|
||||
<Button onClick={this.props.onDismiss}>Done</Button>
|
||||
<Button onClick={this.props.onDismiss} isTransparent={true}>Connect a different wallet</Button>
|
||||
</Confirmation>
|
||||
</StyledDialogContent>
|
||||
</DialogOverlay>
|
||||
</>
|
||||
);
|
||||
}
|
||||
public _renderFormContent(errors: ErrorProps): React.ReactNode {
|
||||
switch (this.state.isWalletConnected) {
|
||||
case true:
|
||||
return this._renderVoteFormContent(errors);
|
||||
case false:
|
||||
default:
|
||||
return this._renderConnectWalletFormContent(errors);
|
||||
}
|
||||
}
|
||||
private _renderConnectWalletFormContent(errors: ErrorProps): React.ReactNode {
|
||||
return (
|
||||
<>
|
||||
<Heading color={colors.textDarkPrimary} size={34} asElement="h2">
|
||||
Connect your Wallet
|
||||
</Heading>
|
||||
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
|
||||
In order to vote on this issue you will need to connect a wallet with a balance of ZRX tokens.
|
||||
</Paragraph>
|
||||
<ButtonRow>
|
||||
<ButtonFull onClick={this._onConnectSimpleAsync.bind(this)}>Connect Metamask</ButtonFull>
|
||||
</ButtonRow>
|
||||
{_.isUndefined(errors.walletError) && (
|
||||
<Paragraph isMuted={true} color={colors.red}>
|
||||
{errors.walletError}
|
||||
</Paragraph>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
private _renderVoteFormContent(errors: ErrorProps): React.ReactNode {
|
||||
const { currentBalance, votePreference } = this.state;
|
||||
return (
|
||||
<>
|
||||
<Heading color={colors.textDarkPrimary} size={34} asElement="h2">
|
||||
MultiAssetProxy Vote
|
||||
</Heading>
|
||||
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
|
||||
Make sure you are informed to the best of your ability before casting your vote. It will have lasting implications for the 0x ecosystem.
|
||||
</Paragraph>
|
||||
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
|
||||
<strong>Voting balance:</strong> {currentBalance} ZRX
|
||||
</Paragraph>
|
||||
<ButtonRow>
|
||||
<ButtonActive isActive={votePreference === VoteValue.Yes} activeColor={colors.brandLight} onClick={this._setVoteYesPreference.bind(this)}>Yes (Accept)</ButtonActive>
|
||||
<ButtonActive isActive={votePreference === VoteValue.No} activeColor="#CF4B00" onClick={this._setVoteNoPreference.bind(this)}>No (Reject)</ButtonActive>
|
||||
</ButtonRow>
|
||||
<InputRow>
|
||||
<Input
|
||||
name="comments"
|
||||
label="Add a comment (Optional)"
|
||||
type="textarea"
|
||||
ref={this.commentsRef}
|
||||
errors={errors}
|
||||
/>
|
||||
</InputRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
private async _onConnectAsync(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
|
||||
const shouldUserLedgerProvider = true;
|
||||
const networkId = 1;
|
||||
try {
|
||||
const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync();
|
||||
const [provider, ledgerSubproviderIfExists] = await this._getProviderAsync(
|
||||
injectedProviderIfExists,
|
||||
networkId,
|
||||
shouldUserLedgerProvider,
|
||||
);
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this.networkId = await this._web3Wrapper.getNetworkIdAsync();
|
||||
// debugger;
|
||||
|
||||
const providerEngine = new Web3ProviderEngine();
|
||||
// const ledgerProvider = new LedgerSubprovider();
|
||||
const web3Wrapper = new Web3Wrapper(providerEngine);
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
|
||||
console.log(accounts);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
/*
|
||||
if (window.ethereum) {
|
||||
window.web3 = new Web3(ethereum);
|
||||
|
||||
try {
|
||||
// Request account access if needed
|
||||
await ethereum.enable();
|
||||
|
||||
this.setState({ ...this.state, isWalletConnected: true });
|
||||
|
||||
if (this.props.onWalletConnected) {
|
||||
this.props.onWalletConnected();
|
||||
}
|
||||
} catch (error) {
|
||||
// User denied account access...
|
||||
console.log(error);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
private async _onConnectSimpleAsync(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
// const injectedProvider = undefined;
|
||||
const injectedProvider = await this._getInjectedProviderIfExistsAsync();
|
||||
|
||||
if (!_.isUndefined(injectedProvider)) {
|
||||
const providerName = this._getNameGivenProvider(injectedProvider);
|
||||
const injectedWeb3IfExists = (window as any).web3;
|
||||
const balance = await this._getZrxBalanceAsync(injectedWeb3IfExists.eth.defaultAccount);
|
||||
|
||||
this.setState({ ...this.state, isWalletConnected: true, providerName, currentAddress: injectedProvider, currentBalance: balance });
|
||||
|
||||
if (this.props.onWalletConnected) {
|
||||
this.props.onWalletConnected(providerName);
|
||||
}
|
||||
} else {
|
||||
this.setState({ ...this.state, isWalletConnected: false, errors: { walletError: 'Could not connected wallet or Ledger.' }});
|
||||
}
|
||||
}
|
||||
private async _getZrxBalanceAsync(walletAddress: string): Promise<string> {
|
||||
// const injectedProvider = undefined;
|
||||
const injectedProvider = await this._getInjectedProviderIfExistsAsync();
|
||||
|
||||
if (!_.isUndefined(injectedProvider)) {
|
||||
// NETWORK_CONFIGS.networkId
|
||||
const contractAddresses = getContractAddressesForNetworkOrThrow(3);
|
||||
const tokenAddress: string = contractAddresses.zrxToken;
|
||||
|
||||
// Get ERC20 Token contract instance
|
||||
const injectedWeb3IfExists = (window as any).web3;
|
||||
const contractWrapper = new ContractWrappers(injectedProvider, { networkId: 3 });
|
||||
|
||||
try {
|
||||
const amount = await contractWrapper.erc20Token.getBalanceAsync(tokenAddress, walletAddress);
|
||||
const formattedAmount = utils.getFormattedAmount(amount, constants.DECIMAL_PLACES_ETH);
|
||||
|
||||
return formattedAmount;
|
||||
} catch (error) {
|
||||
return utils.getFormattedAmount(new BigNumber(0), constants.DECIMAL_PLACES_ETH);
|
||||
}
|
||||
}
|
||||
|
||||
return utils.getFormattedAmount(new BigNumber(0), constants.DECIMAL_PLACES_ETH);
|
||||
}
|
||||
private async _voteNewAsync(e: FormEvent): Promise<string> {
|
||||
const domain = [
|
||||
{ name: 'name', type: 'string' },
|
||||
];
|
||||
const vote = [
|
||||
{ name: 'preference', type: 'string' },
|
||||
{ name: 'zeip', type: 'uint256' },
|
||||
{ name: 'title', type: 'string' },
|
||||
];
|
||||
const domainData = {
|
||||
name: '0x Protocol Governance',
|
||||
};
|
||||
const message = {
|
||||
preference: 'Yes',
|
||||
zeip: '23',
|
||||
title: 'MultiAssetProxy: Allow multiple assets per side of a single order',
|
||||
};
|
||||
const types = {
|
||||
EIP712Domain: domain,
|
||||
Vote: vote,
|
||||
};
|
||||
const typedData = {
|
||||
types: {
|
||||
EIP712Domain: domain,
|
||||
Vote: vote,
|
||||
},
|
||||
domain: domainData,
|
||||
message,
|
||||
primaryType: 'Vote',
|
||||
};
|
||||
|
||||
const voteHashBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
|
||||
// console.log(`0x${voteHashBuffer.toString('hex')}`);
|
||||
// 0x4afb05e4654fa3daed96183f581b4e9637e3ebe1da182a99f7a2cf48b51a129a
|
||||
|
||||
// Instantiate wrapper
|
||||
const injectedWeb3IfExists = (window as any).web3;
|
||||
const providerEngine = new Web3ProviderEngine();
|
||||
// Compose our Providers, order matters
|
||||
// Use the SignerSubprovider to wrap the browser extension wallet
|
||||
// All account based and signing requests will go through the SignerSubprovider
|
||||
providerEngine.addProvider(new SignerSubprovider(injectedWeb3IfExists.currentProvider));
|
||||
// Use an RPC provider to route all other requests
|
||||
// providerEngine.addProvider(new RPCSubprovider('http://localhost:8545'));
|
||||
providerEngine.start();
|
||||
|
||||
const web3Wrapper = new Web3Wrapper(providerEngine);
|
||||
|
||||
// Sign voteHashBuffer
|
||||
// const injectedWeb3IfExists = (window as any).web3;
|
||||
const signerAddress = injectedWeb3IfExists.eth.coinbase;
|
||||
|
||||
debugger;
|
||||
const signature = await web3Wrapper.signTypedDataAsync(signerAddress, typedData);
|
||||
|
||||
const ecSignatureRSV = this._parseSignatureHexAsRSV(signature); // can be found in signature utils but not exported
|
||||
const signatureBuffer = Buffer.concat([
|
||||
ethUtil.toBuffer(ecSignatureRSV.v),
|
||||
ethUtil.toBuffer(ecSignatureRSV.r),
|
||||
ethUtil.toBuffer(ecSignatureRSV.s),
|
||||
ethUtil.toBuffer(SignatureType.EIP712), // append the signature type byte
|
||||
]);
|
||||
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||
|
||||
console.log(signatureHex);
|
||||
|
||||
const isValid = await signatureUtils.isValidSignatureAsync(injectedWeb3IfExists.currentProvider, signatureHex, signature, signerAddress);
|
||||
|
||||
debugger;
|
||||
console.log(signatureHex, isValid);
|
||||
|
||||
return signatureHex;
|
||||
|
||||
}
|
||||
/*private async _voteAsync(e: FormEvent): Promise<string> {
|
||||
const { votePreference } = this.state;
|
||||
const domainType = [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
const voteType = [
|
||||
{
|
||||
name: 'campaignId',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'preference',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
const domainData = {
|
||||
name: '0x V0x',
|
||||
version: '1',
|
||||
};
|
||||
const voteData = {
|
||||
campaignId: '1',
|
||||
preference: votePreference,
|
||||
};
|
||||
|
||||
const zdata = JSON.stringify({
|
||||
types: {
|
||||
EIP712Domain: domainType,
|
||||
Vote: voteType,
|
||||
},
|
||||
domain: domainData,
|
||||
primaryType: 'Vote',
|
||||
message: voteData,
|
||||
});
|
||||
|
||||
const injectedProvider = await this._getInjectedProviderIfExistsAsync();
|
||||
const zrxContractAddress = '0xE41d2489571d322189246DaFA5ebDe1F4699F498';
|
||||
const injectedWeb3IfExists = (window as any).web3;
|
||||
const signer = injectedWeb3IfExists.eth.coinbase;
|
||||
console.log(zdata, signer, votePreference);
|
||||
|
||||
const payload: JSONRPCPayload = {
|
||||
method: 'eth_signTypedData_v3',
|
||||
params: [
|
||||
signer, zdata,
|
||||
],
|
||||
// from: signer,
|
||||
};
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
function done(error: string, result: string) {
|
||||
if (error) {
|
||||
//this.setState({ ...this.state, isSuccessful: true });
|
||||
|
||||
console.error(error);
|
||||
reject(error);
|
||||
} else {
|
||||
console.log(result);
|
||||
resolve(result.result);
|
||||
}
|
||||
}
|
||||
// injectedProvider
|
||||
injectedProvider
|
||||
.sendAsync(payload, done);
|
||||
});
|
||||
}*/
|
||||
private async _onSubmitAsync(e: FormEvent): Promise<void> {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.state.votePreference) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// const signature = this._voteAsync(e);
|
||||
const signature = await this._voteNewAsync(e);
|
||||
|
||||
this.setState({ ...this.state, isSuccessful: true });
|
||||
|
||||
if (this.props.onVoted) {
|
||||
this.props.onVoted();
|
||||
}
|
||||
|
||||
const injectedWeb3IfExists = (window as any).web3;
|
||||
const jsonBody = JSON.stringify({
|
||||
preference: this.state.votePreference,
|
||||
voterAddress: injectedWeb3IfExists.eth.coinbase,
|
||||
signature,
|
||||
comment: this.commentsRef.current.value,
|
||||
campaignId: 1,
|
||||
});
|
||||
console.log(jsonBody);
|
||||
} catch (error) {
|
||||
console.log('Error', error);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
this.setState({ ...this.state, errors: [], isSubmitting: true });
|
||||
|
||||
const endpoint = '';
|
||||
const jsonBody = {};
|
||||
|
||||
try {
|
||||
// Disabling no-unbound method b/c no reason for _.isEmpty to be bound
|
||||
// tslint:disable:no-unbound-method
|
||||
const response = await fetch(`${utils.getBackendBaseUrl()}${endpoint}`, {
|
||||
method: 'post',
|
||||
mode: 'cors',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
},
|
||||
body: JSON.stringify(_.omitBy(jsonBody, _.isEmpty)),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorResponse: ErrorResponse = await response.json();
|
||||
const errors = this._parseErrors(errorResponse.errors);
|
||||
this.setState({ ...this.state, isSubmitting: false, errors });
|
||||
|
||||
throw new Error('Request failed');
|
||||
}
|
||||
|
||||
this.setState({ ...this.state, isSuccessful: true });
|
||||
} catch (e) {
|
||||
// Empty block
|
||||
}
|
||||
}
|
||||
private _parseErrors(errors: ErrorResponseProps[]): ErrorProps {
|
||||
const initialValue: {} = {};
|
||||
return _.reduce(
|
||||
errors,
|
||||
(hash: ErrorProps, error: ErrorResponseProps) => {
|
||||
const { param, msg } = error;
|
||||
const key = param;
|
||||
hash[key] = msg;
|
||||
|
||||
return hash;
|
||||
},
|
||||
initialValue,
|
||||
);
|
||||
}
|
||||
private async _getProviderAsync(
|
||||
injectedProviderIfExists?: InjectedProvider,
|
||||
networkIdIfExists?: number,
|
||||
shouldUserLedgerProvider: boolean = false,
|
||||
): Promise<[Provider | undefined, LedgerSubprovider | undefined]> {
|
||||
const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists);
|
||||
const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists);
|
||||
const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists];
|
||||
const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId);
|
||||
|
||||
if (shouldUserLedgerProvider && isNetworkIdAvailable) {
|
||||
const isU2FSupported = await utils.isU2FSupportedAsync();
|
||||
if (!isU2FSupported) {
|
||||
throw new Error('Cannot update providerType to LEDGER without U2F support');
|
||||
}
|
||||
const provider = new Web3ProviderEngine();
|
||||
const ledgerWalletConfigs = {
|
||||
networkId: networkIdIfExists,
|
||||
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
|
||||
};
|
||||
const ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
|
||||
provider.addProvider(ledgerSubprovider);
|
||||
provider.addProvider(new FilterSubprovider());
|
||||
/* const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists], publicNodeUrl => {
|
||||
return new RPCSubprovider(publicNodeUrl);
|
||||
});
|
||||
provider.addProvider(new RedundantSubprovider(rpcSubproviders)); */
|
||||
provider.start();
|
||||
return [provider, ledgerSubprovider];
|
||||
} else if (doesInjectedProviderExist && isPublicNodeAvailableForNetworkId) {
|
||||
// We catch all requests involving a users account and send it to the injectedWeb3
|
||||
// instance. All other requests go to the public hosted node.
|
||||
const provider = new Web3ProviderEngine();
|
||||
const providerName = this._getNameGivenProvider(injectedProviderIfExists);
|
||||
// Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies)
|
||||
const signerSubprovider =
|
||||
providerName === constants.PROVIDER_NAME_METAMASK
|
||||
? new MetamaskSubprovider(injectedProviderIfExists)
|
||||
: new SignerSubprovider(injectedProviderIfExists);
|
||||
provider.addProvider(signerSubprovider);
|
||||
provider.addProvider(new FilterSubprovider());
|
||||
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
|
||||
return new RPCSubprovider(publicNodeUrl);
|
||||
});
|
||||
provider.addProvider(new RedundantSubprovider(rpcSubproviders));
|
||||
provider.start();
|
||||
return [provider, undefined];
|
||||
} else if (doesInjectedProviderExist) {
|
||||
// Since no public node for this network, all requests go to injectedWeb3 instance
|
||||
return [injectedProviderIfExists, undefined];
|
||||
} else {
|
||||
return [undefined, undefined];
|
||||
}
|
||||
}
|
||||
private async _getInjectedProviderIfExistsAsync(): Promise<InjectedProvider | undefined> {
|
||||
if (!_.isUndefined(this._injectedProviderIfExists)) {
|
||||
return this._injectedProviderIfExists;
|
||||
}
|
||||
let injectedProviderIfExists = (window as any).ethereum;
|
||||
if (!_.isUndefined(injectedProviderIfExists)) {
|
||||
if (!_.isUndefined(injectedProviderIfExists.enable)) {
|
||||
try {
|
||||
await injectedProviderIfExists.enable();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
//errorReporter.report(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const injectedWeb3IfExists = (window as any).web3;
|
||||
if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) {
|
||||
injectedProviderIfExists = injectedWeb3IfExists.currentProvider;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
this._injectedProviderIfExists = injectedProviderIfExists;
|
||||
return injectedProviderIfExists;
|
||||
}
|
||||
private async _getInjectedProviderNetworkIdIfExistsAsync(): Promise<number | undefined> {
|
||||
// If the user has an injectedWeb3 instance that is disconnected from a backing
|
||||
// Ethereum node, this call will throw. We need to handle this case gracefully
|
||||
const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync();
|
||||
let networkIdIfExists: number;
|
||||
if (!_.isUndefined(injectedProviderIfExists)) {
|
||||
try {
|
||||
const injectedWeb3Wrapper = new Web3Wrapper(injectedProviderIfExists);
|
||||
networkIdIfExists = await injectedWeb3Wrapper.getNetworkIdAsync();
|
||||
} catch (err) {
|
||||
// Ignore error and proceed with networkId undefined
|
||||
}
|
||||
}
|
||||
return networkIdIfExists;
|
||||
}
|
||||
private _getNameGivenProvider(provider: Provider): string {
|
||||
const providerType = utils.getProviderType(provider);
|
||||
const providerNameIfExists = providerToName[providerType];
|
||||
if (_.isUndefined(providerNameIfExists)) {
|
||||
return constants.PROVIDER_NAME_GENERIC;
|
||||
}
|
||||
return providerNameIfExists;
|
||||
}
|
||||
private _setVoteYesPreference(e: Event): void {
|
||||
e.preventDefault();
|
||||
this._setVotePreference(VoteValue.Yes);
|
||||
}
|
||||
private _setVoteNoPreference(e: Event): void {
|
||||
e.preventDefault();
|
||||
this._setVotePreference(VoteValue.No);
|
||||
}
|
||||
private _setVotePreference(value: VoteValue.No | VoteValue.Yes): void {
|
||||
this.setState({ ...this.state, votePreference: value });
|
||||
}
|
||||
private _parseSignatureHexAsRSV(signatureHex: string): ECSignature {
|
||||
const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
|
||||
const ecSignature: ECSignature = {
|
||||
v,
|
||||
r: ethUtil.bufferToHex(r),
|
||||
s: ethUtil.bufferToHex(s),
|
||||
};
|
||||
return ecSignature;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors: {"errors":[{"location":"body","param":"name","msg":"Invalid value"},{"location":"body","param":"email","msg":"Invalid value"}]}
|
||||
|
||||
const InputRow = styled.div`
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonRow = styled(InputRow)`
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
button:nth-child(1) {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
button:nth-child(2) {
|
||||
order: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonFull = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ButtonHalf = styled(Button)`
|
||||
width: calc(50% - 15px);
|
||||
`;
|
||||
|
||||
const ButtonDisabled = styled(Button)<{ isDisabled?: boolean; disabled?: boolean }>`
|
||||
background-color: ${props => props.disabled && '#898990'};
|
||||
opacity: ${props => props.disabled && '0.4'};
|
||||
`;
|
||||
|
||||
const ButtonActive = styled(Button)<{ isActive: boolean; activeColor: string; onClickValue?: string }>`
|
||||
background-color: ${props => props.isActive ? props.activeColor : '#898990'};
|
||||
width: calc(50% - 15px);
|
||||
|
||||
&:hover {
|
||||
background-color: ${props => props.activeColor ? props.activeColor : '#898990'};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDialogContent = styled(DialogContent)`
|
||||
position: relative;
|
||||
max-width: 800px;
|
||||
background-color: #f6f6f6 !important;
|
||||
padding: 60px 60px !important;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: calc(100vw - 40px) !important;
|
||||
margin: 40px auto !important;
|
||||
padding: 30px 30px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const Form = styled.form<FormProps>`
|
||||
position: relative;
|
||||
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
|
||||
|
||||
opacity: ${props => props.isSuccessful && `0`};
|
||||
visibility: ${props => props.isSuccessful && `hidden`};
|
||||
`;
|
||||
|
||||
const Confirmation = styled.div<FormProps>`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
|
||||
transition-delay: 0.4s;
|
||||
padding: 60px 60px;
|
||||
transform: translateY(-50%);
|
||||
opacity: ${props => (props.isSuccessful ? `1` : `0`)};
|
||||
visibility: ${props => (props.isSuccessful ? 'visible' : `hidden`)};
|
||||
|
||||
p {
|
||||
max-width: 492px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
`;
|
@ -365,6 +365,7 @@ export enum WebsitePaths {
|
||||
Instant = '/instant',
|
||||
Ecosystem = '/eap',
|
||||
MarketMaker = '/market-maker',
|
||||
Governance = '/governance',
|
||||
Why = '/why',
|
||||
Whitepaper = '/pdfs/0x_white_paper.pdf',
|
||||
SmartContracts = '/docs/contracts',
|
||||
|
Loading…
x
Reference in New Issue
Block a user