aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/pages
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/pages')
-rw-r--r--packages/website/ts/pages/about/about.tsx253
-rw-r--r--packages/website/ts/pages/about/profile.tsx99
-rw-r--r--packages/website/ts/pages/documentation/comment.tsx24
-rw-r--r--packages/website/ts/pages/documentation/custom_enum.tsx31
-rw-r--r--packages/website/ts/pages/documentation/enum.tsx26
-rw-r--r--packages/website/ts/pages/documentation/event_definition.tsx80
-rw-r--r--packages/website/ts/pages/documentation/interface.tsx54
-rw-r--r--packages/website/ts/pages/documentation/method_block.tsx174
-rw-r--r--packages/website/ts/pages/documentation/method_signature.tsx62
-rw-r--r--packages/website/ts/pages/documentation/smart_contracts_documentation.tsx401
-rw-r--r--packages/website/ts/pages/documentation/source_link.tsx27
-rw-r--r--packages/website/ts/pages/documentation/type.tsx187
-rw-r--r--packages/website/ts/pages/documentation/type_definition.tsx135
-rw-r--r--packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx340
-rw-r--r--packages/website/ts/pages/faq/faq.tsx497
-rw-r--r--packages/website/ts/pages/faq/question.tsx52
-rw-r--r--packages/website/ts/pages/landing/landing.tsx843
-rw-r--r--packages/website/ts/pages/not_found.tsx46
-rw-r--r--packages/website/ts/pages/shared/anchor_title.tsx98
-rw-r--r--packages/website/ts/pages/shared/markdown_code_block.tsx20
-rw-r--r--packages/website/ts/pages/shared/markdown_section.tsx77
-rw-r--r--packages/website/ts/pages/shared/nested_sidebar_menu.tsx163
-rw-r--r--packages/website/ts/pages/shared/section_header.tsx50
-rw-r--r--packages/website/ts/pages/shared/version_drop_down.tsx46
-rw-r--r--packages/website/ts/pages/wiki/wiki.tsx210
25 files changed, 3995 insertions, 0 deletions
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
new file mode 100644
index 000000000..8859fb00a
--- /dev/null
+++ b/packages/website/ts/pages/about/about.tsx
@@ -0,0 +1,253 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {Styles, ProfileInfo} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {Link} from 'react-router-dom';
+import {Footer} from 'ts/components/footer';
+import {TopBar} from 'ts/components/top_bar';
+import {Question} from 'ts/pages/faq/question';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import {Profile} from 'ts/pages/about/profile';
+
+const CUSTOM_BACKGROUND_COLOR = '#F0F0F0';
+const CUSTOM_GRAY = '#4C4C4C';
+const CUSTOM_LIGHT_GRAY = '#A2A2A2';
+
+const teamRow1: ProfileInfo[] = [
+ {
+ name: 'Will Warren',
+ title: 'Co-founder & CEO',
+ description: `Smart contract R&D. Previously applied physics at Los Alamos \
+ Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`,
+ image: '/images/team/will.jpg',
+ linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/',
+ github: 'https://github.com/willwarren89',
+ medium: 'https://medium.com/@willwarren89',
+ },
+ {
+ name: 'Amir Bandeali',
+ title: 'Co-founder & CTO',
+ description: `Smart contract R&D. Previously fixed income trader at DRW. \
+ Finance at University of Illinois, Urbana-Champaign.`,
+ image: '/images/team/amir.jpeg',
+ linkedIn: 'https://www.linkedin.com/in/abandeali1/',
+ github: 'https://github.com/abandeali1',
+ medium: 'https://medium.com/@abandeali1',
+ },
+ {
+ name: 'Fabio Berger',
+ title: 'Senior Engineer',
+ description: `Full-stack blockchain engineer. Previously software engineer \
+ at Airtable and founder of WealthLift. Computer science at Duke.`,
+ image: '/images/team/fabio.jpg',
+ linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/',
+ github: 'https://github.com/fabioberger',
+ medium: 'https://medium.com/@fabioberger',
+ },
+ {
+ name: 'Alex Xu',
+ title: 'Director of Operations',
+ description: `Strategy and operations. Previously digital marketing at Google \
+ and vendor management at Amazon. Economics at UC San Diego.`,
+ image: '/images/team/alex.jpg',
+ linkedIn: 'https://www.linkedin.com/in/alex-xu/',
+ github: '',
+ medium: '',
+ },
+];
+
+const teamRow2: ProfileInfo[] = [
+ {
+ name: 'Leonid Logvinov',
+ title: 'Engineer',
+ description: `Full-stack blockchain engineer. Previously blockchain engineer \
+ at Neufund. Computer science at University of Warsaw.`,
+ image: '/images/team/leonid.png',
+ linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/',
+ github: 'https://github.com/LogvinovLeon',
+ medium: '',
+ },
+ {
+ name: 'Ben Burns',
+ title: 'Designer',
+ description: `Product, motion, and graphic designer. Previously designer \
+ at Airtable and Apple. Digital Design at University of Cincinnati.`,
+ image: '/images/team/ben.jpg',
+ linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/',
+ github: '',
+ medium: '',
+ },
+ {
+ name: 'Philippe Castonguay',
+ title: 'Dev Relations Manager',
+ description: `Developer relations. Previously computational neuroscience \
+ research at Janelia. Statistics at Western University. MA Dropout.`,
+ image: '/images/team/philippe.png',
+ linkedIn: '',
+ github: 'https://github.com/PhABC',
+ medium: '',
+ },
+ {
+ name: 'Brandon Millman',
+ title: 'Senior Engineer',
+ description: `Full-stack engineer. Previously senior software engineer at \
+ Twitter. Electrical and Computer Engineering at Duke.`,
+ image: '/images/team/brandon.png',
+ linkedIn: 'https://www.linkedin.com/company-beta/17942619/',
+ },
+];
+
+const advisors: ProfileInfo[] = [
+ {
+ name: 'Fred Ehrsam',
+ description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.',
+ image: '/images/advisors/fred.jpg',
+ linkedIn: 'https://www.linkedin.com/in/fredehrsam/',
+ medium: 'https://medium.com/@FEhrsam',
+ twitter: 'https://twitter.com/FEhrsam',
+ },
+ {
+ name: 'Olaf Carlson-Wee',
+ image: '/images/advisors/olaf.png',
+ description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.',
+ linkedIn: 'https://www.linkedin.com/in/olafcw/',
+ angellist: 'https://angel.co/olafcw',
+ },
+ {
+ name: 'Joey Krug',
+ description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`,
+ image: '/images/advisors/joey.jpg',
+ linkedIn: 'https://www.linkedin.com/in/joeykrug/',
+ github: 'https://github.com/joeykrug',
+ angellist: 'https://angel.co/joeykrug',
+ },
+ {
+ name: 'Linda Xie',
+ description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.',
+ image: '/images/advisors/linda.jpg',
+ linkedIn: 'https://www.linkedin.com/in/lindaxie/',
+ medium: 'https://medium.com/@linda.xie',
+ twitter: 'https://twitter.com/ljxie',
+ },
+];
+
+export interface AboutProps {
+ source: string;
+ location: Location;
+}
+
+interface AboutState {}
+
+const styles: Styles = {
+ header: {
+ fontFamily: 'Roboto Mono',
+ fontSize: 36,
+ color: 'black',
+ paddingTop: 110,
+ },
+};
+
+export class About extends React.Component<AboutProps, AboutState> {
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div style={{backgroundColor: CUSTOM_BACKGROUND_COLOR}}>
+ <DocumentTitle title="0x About Us"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ style={{backgroundColor: CUSTOM_BACKGROUND_COLOR}}
+ />
+ <div
+ id="about"
+ className="mx-auto max-width-4 py4"
+ style={{color: colors.grey800}}
+ >
+ <div
+ className="mx-auto pb4 sm-px3"
+ style={{maxWidth: 435}}
+ >
+ <div
+ style={styles.header}
+ >
+ About us:
+ </div>
+ <div
+ className="pt3"
+ style={{fontSize: 17, color: CUSTOM_GRAY, lineHeight: 1.5}}
+ >
+ Our team is a diverse and globally distributed group with backgrounds
+ in engineering, research, business and design. We are passionate about
+ decentralized technology and its potential to act as an equalizing force
+ in the world.
+ </div>
+ </div>
+ <div className="pt3 md-px4 lg-px0">
+ <div className="clearfix pb3">
+ {this.renderProfiles(teamRow1)}
+ </div>
+ <div className="clearfix">
+ {this.renderProfiles(teamRow2)}
+ </div>
+ </div>
+ <div className="pt3 pb2">
+ <div
+ className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3"
+ style={{color: CUSTOM_LIGHT_GRAY, fontSize: 24, fontFamily: 'Roboto Mono'}}
+ >
+ Advisors:
+ </div>
+ <div className="clearfix">
+ {this.renderProfiles(advisors)}
+ </div>
+ </div>
+ <div className="mx-auto py4 sm-px3" style={{maxWidth: 308}}>
+ <div
+ className="pb2"
+ style={{fontSize: 30, color: CUSTOM_GRAY, fontFamily: 'Roboto Mono', letterSpacing: 7.5}}
+ >
+ WE'RE HIRING
+ </div>
+ <div
+ className="pb4 mb4"
+ style={{fontSize: 16, color: CUSTOM_GRAY, lineHeight: 1.5, letterSpacing: '0.5px'}}
+ >
+ We are seeking outstanding candidates to{' '}
+ <a
+ href={constants.ANGELLIST_URL}
+ target="_blank"
+ style={{color: 'black'}}
+ >
+ join our team
+ </a>
+ . We value passion, diversity and unique perspectives.
+ </div>
+ </div>
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderProfiles(profiles: ProfileInfo[]) {
+ const numIndiv = profiles.length;
+ const colSize = utils.getColSize(profiles.length);
+ return _.map(profiles, profile => {
+ return (
+ <div
+ key={`profile-${profile.name}`}
+ >
+ <Profile
+ colSize={colSize}
+ profileInfo={profile}
+ />
+ </div>
+ );
+ });
+ }
+}
diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx
new file mode 100644
index 000000000..6c48a8553
--- /dev/null
+++ b/packages/website/ts/pages/about/profile.tsx
@@ -0,0 +1,99 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {utils} from 'ts/utils/utils';
+import {Element as ScrollElement} from 'react-scroll';
+import {Styles, ProfileInfo} from 'ts/types';
+
+const IMAGE_DIMENSION = 149;
+const styles: Styles = {
+ subheader: {
+ textTransform: 'uppercase',
+ fontSize: 32,
+ margin: 0,
+ },
+ imageContainer: {
+ width: IMAGE_DIMENSION,
+ height: IMAGE_DIMENSION,
+ boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px',
+ },
+};
+
+interface ProfileProps {
+ colSize: number;
+ profileInfo: ProfileInfo;
+}
+
+export function Profile(props: ProfileProps) {
+ return (
+ <div
+ className={`lg-col md-col lg-col-${props.colSize} md-col-6`}
+ >
+ <div
+ style={{maxWidth: 300}}
+ className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"
+ >
+ <div
+ className="circle overflow-hidden mx-auto"
+ style={styles.imageContainer}
+ >
+ <img
+ width={IMAGE_DIMENSION}
+ src={props.profileInfo.image}
+ />
+ </div>
+ <div
+ className="center"
+ style={{fontSize: 18, fontWeight: 'bold', paddingTop: 20}}
+ >
+ {props.profileInfo.name}
+ </div>
+ {!_.isUndefined(props.profileInfo.title) &&
+ <div
+ className="pt1 center"
+ style={{fontSize: 14, fontFamily: 'Roboto Mono', color: '#818181'}}
+ >
+ {props.profileInfo.title.toUpperCase()}
+ </div>
+ }
+ <div
+ style={{minHeight: 60, lineHeight: 1.4}}
+ className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"
+ >
+ {props.profileInfo.description}
+ </div>
+ <div className="flex pb3 mx-auto sm-hide xs-hide" style={{width: 180, opacity: 0.5}}>
+ {renderSocialMediaIcons(props.profileInfo)}
+ </div>
+ </div>
+ </div>
+ );
+}
+
+function renderSocialMediaIcons(profileInfo: ProfileInfo) {
+ const icons = [
+ renderSocialMediaIcon('zmdi-github-box', profileInfo.github),
+ renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn),
+ renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter),
+ ];
+ return icons;
+}
+
+function renderSocialMediaIcon(iconName: string, url: string) {
+ if (_.isEmpty(url)) {
+ return null;
+ }
+
+ return (
+ <div key={url} className="pr1">
+ <a
+ href={url}
+ style={{color: 'inherit'}}
+ target="_blank"
+ className="text-decoration-none"
+ >
+ <i className={`zmdi ${iconName}`} style={{...styles.socalIcon}} />
+ </a>
+ </div>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx
new file mode 100644
index 000000000..78bbdc069
--- /dev/null
+++ b/packages/website/ts/pages/documentation/comment.tsx
@@ -0,0 +1,24 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block';
+
+interface CommentProps {
+ comment: string;
+ className?: string;
+}
+
+const defaultProps = {
+ className: '',
+};
+
+export const Comment: React.SFC<CommentProps> = (props: CommentProps) => {
+ return (
+ <div className={`${props.className} comment`}>
+ <ReactMarkdown
+ source={props.comment}
+ renderers={{CodeBlock: MarkdownCodeBlock}}
+ />
+ </div>
+ );
+};
diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/website/ts/pages/documentation/custom_enum.tsx
new file mode 100644
index 000000000..aca8af832
--- /dev/null
+++ b/packages/website/ts/pages/documentation/custom_enum.tsx
@@ -0,0 +1,31 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {utils} from 'ts/utils/utils';
+import {CustomType} from 'ts/types';
+
+const STRING_ENUM_CODE_PREFIX = ' strEnum(';
+
+interface CustomEnumProps {
+ type: CustomType;
+}
+
+// This component renders custom string enums that was a work-around for versions of
+// TypeScript <2.4.0 that did not support them natively. We keep it around to support
+// older versions of 0x.js <0.9.0
+export function CustomEnum(props: CustomEnumProps) {
+ const type = props.type;
+ if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) {
+ utils.consoleLog('We do not yet support `Variable` types that are not strEnums');
+ return null;
+ }
+ // Remove the prefix and postfix, leaving only the strEnum values without quotes.
+ const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, '');
+ return (
+ <span>
+ {`{`}
+ {'\t'}{enumValues}
+ <br />
+ {`}`}
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/enum.tsx b/packages/website/ts/pages/documentation/enum.tsx
new file mode 100644
index 000000000..9364a5d31
--- /dev/null
+++ b/packages/website/ts/pages/documentation/enum.tsx
@@ -0,0 +1,26 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {utils} from 'ts/utils/utils';
+import {TypeDocNode, EnumValue} from 'ts/types';
+
+const STRING_ENUM_CODE_PREFIX = ' strEnum(';
+
+interface EnumProps {
+ values: EnumValue[];
+}
+
+export function Enum(props: EnumProps) {
+ const values = _.map(props.values, (value, i) => {
+ const isLast = i === props.values.length - 1;
+ const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : '';
+ return `\n\t${value.name}${defaultValueIfAny},`;
+ });
+ return (
+ <span>
+ {`{`}
+ {values}
+ <br />
+ {`}`}
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx
new file mode 100644
index 000000000..58271e98f
--- /dev/null
+++ b/packages/website/ts/pages/documentation/event_definition.tsx
@@ -0,0 +1,80 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {Event, EventArg, HeaderSizes} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+
+const KEYWORD_COLOR = '#a81ca6';
+const CUSTOM_GREEN = 'rgb(77, 162, 75)';
+
+interface EventDefinitionProps {
+ event: Event;
+}
+
+interface EventDefinitionState {
+ shouldShowAnchor: boolean;
+}
+
+export class EventDefinition extends React.Component<EventDefinitionProps, EventDefinitionState> {
+ constructor(props: EventDefinitionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const event = this.props.event;
+ return (
+ <div
+ id={event.name}
+ className="pb2"
+ style={{overflow: 'hidden', width: '100%'}}
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <AnchorTitle
+ headerSize={HeaderSizes.H3}
+ title={`Event ${event.name}`}
+ id={event.name}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ <div style={{fontSize: 16}}>
+ <pre>
+ <code className="hljs">
+ {this.renderEventCode()}
+ </code>
+ </pre>
+ </div>
+ </div>
+ );
+ }
+ private renderEventCode() {
+ const indexed = <span style={{color: CUSTOM_GREEN}}> indexed</span>;
+ const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => {
+ return (
+ <span key={`eventArg-${eventArg.name}`}>
+ {eventArg.name}{eventArg.isIndexed ? indexed : ''}: <Type type={eventArg.type} />,
+ </span>
+ );
+ });
+ const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, '\n\t', curr];
+ });
+ return (
+ <span>
+ {`{`}
+ <br />
+ {'\t'}{argList}
+ <br />
+ {`}`}
+ </span>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/interface.tsx b/packages/website/ts/pages/documentation/interface.tsx
new file mode 100644
index 000000000..9e40b8901
--- /dev/null
+++ b/packages/website/ts/pages/documentation/interface.tsx
@@ -0,0 +1,54 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {CustomType, TypeDocTypes} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+import {MethodSignature} from 'ts/pages/documentation/method_signature';
+
+interface InterfaceProps {
+ type: CustomType;
+}
+
+export function Interface(props: InterfaceProps) {
+ const type = props.type;
+ const properties = _.map(type.children, property => {
+ return (
+ <span key={`property-${property.name}-${property.type}-${type.name}`}>
+ {property.name}:{' '}
+ {property.type.typeDocType !== TypeDocTypes.Reflection ?
+ <Type type={property.type} /> :
+ <MethodSignature
+ method={property.type.method}
+ shouldHideMethodName={true}
+ shouldUseArrowSyntax={true}
+ />
+ },
+ </span>
+ );
+ });
+ const hasIndexSignature = !_.isUndefined(type.indexSignature);
+ if (hasIndexSignature) {
+ const is = type.indexSignature;
+ const param = (
+ <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}>
+ {is.keyName}: <Type type={is.keyType} />
+ </span>
+ );
+ properties.push((
+ <span key={`indexSignature-${type.name}-${is.keyType.name}`}>
+ [{param}]: {is.valueName},
+ </span>
+ ));
+ }
+ const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, '\n\t', curr];
+ });
+ return (
+ <span>
+ {`{`}
+ <br />
+ {'\t'}{propertyList}
+ <br />
+ {`}`}
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx
new file mode 100644
index 000000000..6fead2f47
--- /dev/null
+++ b/packages/website/ts/pages/documentation/method_block.tsx
@@ -0,0 +1,174 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import {Chip} from 'material-ui/Chip';
+import {colors} from 'material-ui/styles';
+import {
+ TypeDocNode,
+ Styles,
+ TypeDefinitionByName,
+ TypescriptMethod,
+ SolidityMethod,
+ Parameter,
+ HeaderSizes,
+} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {SourceLink} from 'ts/pages/documentation/source_link';
+import {MethodSignature} from 'ts/pages/documentation/method_signature';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {Comment} from 'ts/pages/documentation/comment';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+
+interface MethodBlockProps {
+ method: SolidityMethod|TypescriptMethod;
+ libraryVersion: string;
+ typeDefinitionByName: TypeDefinitionByName;
+}
+
+interface MethodBlockState {
+ shouldShowAnchor: boolean;
+}
+
+const styles: Styles = {
+ chip: {
+ fontSize: 13,
+ backgroundColor: colors.lightBlueA700,
+ color: 'white',
+ height: 11,
+ borderRadius: 14,
+ marginTop: 19,
+ lineHeight: 0.8,
+ },
+};
+
+export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> {
+ constructor(props: MethodBlockProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const method = this.props.method;
+ if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) {
+ return null;
+ }
+
+ return (
+ <div
+ id={method.name}
+ style={{overflow: 'hidden', width: '100%'}}
+ className="pb4"
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ {!method.isConstructor &&
+ <div className="flex">
+ {(method as TypescriptMethod).isStatic &&
+ this.renderChip('Static')
+ }
+ {(method as SolidityMethod).isConstant &&
+ this.renderChip('Constant')
+ }
+ {(method as SolidityMethod).isPayable &&
+ this.renderChip('Payable')
+ }
+ <AnchorTitle
+ headerSize={HeaderSizes.H3}
+ title={method.name}
+ id={method.name}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </div>
+ }
+ <code className="hljs">
+ <MethodSignature
+ method={method}
+ typeDefinitionByName={this.props.typeDefinitionByName}
+ />
+ </code>
+ {(method as TypescriptMethod).source &&
+ <SourceLink
+ version={this.props.libraryVersion}
+ source={(method as TypescriptMethod).source}
+ />
+ }
+ {method.comment &&
+ <Comment
+ comment={method.comment}
+ className="py2"
+ />
+ }
+ {method.parameters && !_.isEmpty(method.parameters) &&
+ <div>
+ <h4
+ className="pb1 thin"
+ style={{borderBottom: '1px solid #e1e8ed'}}
+ >
+ ARGUMENTS
+ </h4>
+ {this.renderParameterDescriptions(method.parameters)}
+ </div>
+ }
+ {method.returnComment &&
+ <div className="pt1 comment">
+ <h4
+ className="pb1 thin"
+ style={{borderBottom: '1px solid #e1e8ed'}}
+ >
+ RETURNS
+ </h4>
+ <Comment
+ comment={method.returnComment}
+ />
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderChip(text: string) {
+ return (
+ <div
+ className="p1 mr1"
+ style={styles.chip}
+ >
+ {text}
+ </div>
+ );
+ }
+ private renderParameterDescriptions(parameters: Parameter[]) {
+ const descriptions = _.map(parameters, parameter => {
+ const isOptional = parameter.isOptional;
+ return (
+ <div
+ key={`param-description-${parameter.name}`}
+ className="flex pb1 mb2"
+ style={{borderBottom: '1px solid #f0f4f7'}}
+ >
+ <div className="col lg-col-1 md-col-1 sm-hide xs-hide" />
+ <div className="col lg-col-3 md-col-3 sm-col-12 col-12">
+ <div className="bold">
+ {parameter.name}
+ </div>
+ <div className="pt1" style={{color: colors.grey500, fontSize: 14}}>
+ {isOptional && 'optional'}
+ </div>
+ </div>
+ <div className="col lg-col-8 md-col-8 sm-col-12 col-12">
+ {parameter.comment &&
+ <Comment
+ comment={parameter.comment}
+ />
+ }
+ </div>
+ </div>
+ );
+ });
+ return descriptions;
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/method_signature.tsx b/packages/website/ts/pages/documentation/method_signature.tsx
new file mode 100644
index 000000000..3b5d2ce78
--- /dev/null
+++ b/packages/website/ts/pages/documentation/method_signature.tsx
@@ -0,0 +1,62 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {TypescriptMethod, SolidityMethod, TypeDefinitionByName, Parameter} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+
+interface MethodSignatureProps {
+ method: TypescriptMethod|SolidityMethod;
+ shouldHideMethodName?: boolean;
+ shouldUseArrowSyntax?: boolean;
+ typeDefinitionByName?: TypeDefinitionByName;
+}
+
+const defaultProps = {
+ shouldHideMethodName: false,
+ shouldUseArrowSyntax: false,
+};
+
+export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => {
+ const parameters = renderParameters(props.method, props.typeDefinitionByName);
+ const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, ', ', curr];
+ });
+ const methodName = props.shouldHideMethodName ? '' : props.method.name;
+ const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) ?
+ undefined :
+ renderTypeParameter(props.method, props.typeDefinitionByName);
+ return (
+ <span>
+ {props.method.callPath}{methodName}{typeParameterIfExists}({paramString})
+ {props.shouldUseArrowSyntax ? ' => ' : ': '}
+ {' '}
+ {props.method.returnType &&
+ <Type type={props.method.returnType} typeDefinitionByName={props.typeDefinitionByName}/>
+ }
+ </span>
+ );
+};
+
+function renderParameters(method: TypescriptMethod|SolidityMethod, typeDefinitionByName?: TypeDefinitionByName) {
+ const parameters = method.parameters;
+ const params = _.map(parameters, (p: Parameter) => {
+ const isOptional = p.isOptional;
+ return (
+ <span key={`param-${p.type}-${p.name}`}>
+ {p.name}{isOptional && '?'}: <Type type={p.type} typeDefinitionByName={typeDefinitionByName}/>
+ </span>
+ );
+ });
+ return params;
+}
+
+function renderTypeParameter(method: TypescriptMethod, typeDefinitionByName?: TypeDefinitionByName) {
+ const typeParameter = method.typeParameter;
+ const typeParam = (
+ <span>
+ {`<${typeParameter.name} extends `}
+ <Type type={typeParameter.type} typeDefinitionByName={typeDefinitionByName}/>
+ {`>`}
+ </span>
+ );
+ return typeParam;
+}
diff --git a/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx
new file mode 100644
index 000000000..3e97829c4
--- /dev/null
+++ b/packages/website/ts/pages/documentation/smart_contracts_documentation.tsx
@@ -0,0 +1,401 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import findVersions = require('find-versions');
+import semverSort = require('semver-sort');
+import {colors} from 'material-ui/styles';
+import CircularProgress from 'material-ui/CircularProgress';
+import {
+ scroller,
+} from 'react-scroll';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ SmartContractsDocSections,
+ Styles,
+ DoxityDocObj,
+ TypeDefinitionByName,
+ DocAgnosticFormat,
+ SolidityMethod,
+ Property,
+ CustomType,
+ MenuSubsectionsBySection,
+ Event,
+ Docs,
+ AddressByContractName,
+ Networks,
+ EtherscanLinkSuffixes,
+} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {utils} from 'ts/utils/utils';
+import {docUtils} from 'ts/utils/doc_utils';
+import {constants} from 'ts/utils/constants';
+import {MethodBlock} from 'ts/pages/documentation/method_block';
+import {SourceLink} from 'ts/pages/documentation/source_link';
+import {Type} from 'ts/pages/documentation/type';
+import {TypeDefinition} from 'ts/pages/documentation/type_definition';
+import {MarkdownSection} from 'ts/pages/shared/markdown_section';
+import {Comment} from 'ts/pages/documentation/comment';
+import {Badge} from 'ts/components/ui/badge';
+import {EventDefinition} from 'ts/pages/documentation/event_definition';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {SectionHeader} from 'ts/pages/shared/section_header';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {doxityUtils} from 'ts/utils/doxity_utils';
+/* tslint:disable:no-var-requires */
+const IntroMarkdown = require('md/docs/smart_contracts/introduction');
+/* tslint:enable:no-var-requires */
+
+const SCROLL_TO_TIMEOUT = 500;
+const CUSTOM_PURPLE = '#690596';
+const CUSTOM_RED = '#e91751';
+const CUSTOM_TURQUOIS = '#058789';
+const DOC_JSON_ROOT = constants.S3_SMART_CONTRACTS_DOCUMENTATION_JSON_ROOT;
+
+const sectionNameToMarkdown = {
+ [SmartContractsDocSections.Introduction]: IntroMarkdown,
+};
+const networkNameToColor: {[network: string]: string} = {
+ [Networks.kovan]: CUSTOM_PURPLE,
+ [Networks.ropsten]: CUSTOM_RED,
+ [Networks.mainnet]: CUSTOM_TURQUOIS,
+};
+
+export interface SmartContractsDocumentationAllProps {
+ source: string;
+ location: Location;
+ dispatcher: Dispatcher;
+ docsVersion: string;
+ availableDocVersions: string[];
+}
+
+interface SmartContractsDocumentationState {
+ docAgnosticFormat?: DocAgnosticFormat;
+}
+
+const styles: Styles = {
+ mainContainers: {
+ position: 'absolute',
+ top: 60,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ overflowZ: 'hidden',
+ overflowY: 'scroll',
+ minHeight: 'calc(100vh - 60px)',
+ WebkitOverflowScrolling: 'touch',
+ },
+ menuContainer: {
+ borderColor: colors.grey300,
+ maxWidth: 330,
+ marginLeft: 20,
+ },
+};
+
+export class SmartContractsDocumentation extends
+ React.Component<SmartContractsDocumentationAllProps, SmartContractsDocumentationState> {
+ constructor(props: SmartContractsDocumentationAllProps) {
+ super(props);
+ this.state = {
+ docAgnosticFormat: undefined,
+ };
+ }
+ public componentWillMount() {
+ const pathName = this.props.location.pathname;
+ const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1);
+ const versions = findVersions(lastSegment);
+ const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined;
+ this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
+ }
+ public render() {
+ const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat)
+ ? {}
+ : this.getMenuSubsectionsBySection(this.state.docAgnosticFormat);
+ return (
+ <div>
+ <DocumentTitle title="0x Smart Contract Documentation"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ docsVersion={this.props.docsVersion}
+ availableDocVersions={this.props.availableDocVersions}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ shouldFullWidth={true}
+ doc={Docs.SmartContracts}
+ />
+ {_.isUndefined(this.state.docAgnosticFormat) ?
+ <div
+ className="col col-12"
+ style={styles.mainContainers}
+ >
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{paddingBottom: 11}}>Loading documentation...</div>
+ </div>
+ </div> :
+ <div
+ className="mx-auto flex"
+ style={{color: colors.grey800, height: 43}}
+ >
+ <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
+ <div
+ className="border-right absolute"
+ style={{...styles.menuContainer, ...styles.mainContainers}}
+ >
+ <NestedSidebarMenu
+ selectedVersion={this.props.docsVersion}
+ versions={this.props.availableDocVersions}
+ topLevelMenu={constants.menuSmartContracts}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ doc={Docs.SmartContracts}
+ />
+ </div>
+ </div>
+ <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
+ <div
+ id="documentation"
+ style={styles.mainContainers}
+ className="absolute"
+ >
+ <div id="smartContractsDocs" />
+ <h1 className="md-pl2 sm-pl3">
+ <a href={constants.GITHUB_CONTRACTS_URL} target="_blank">
+ 0x Smart Contracts
+ </a>
+ </h1>
+ {this.renderDocumentation()}
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderDocumentation(): React.ReactNode {
+ const subMenus = _.values(constants.menuSmartContracts);
+ const orderedSectionNames = _.flatten(subMenus);
+ // Since smart contract method params are all base types, no need to pass
+ // down the typeDefinitionByName
+ const typeDefinitionByName = {};
+ const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName));
+
+ return sections;
+ }
+ private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode {
+ const docSection = this.state.docAgnosticFormat[sectionName];
+
+ const markdownFileIfExists = sectionNameToMarkdown[sectionName];
+ if (!_.isUndefined(markdownFileIfExists)) {
+ return (
+ <MarkdownSection
+ key={`markdown-section-${sectionName}`}
+ sectionName={sectionName}
+ markdownContent={markdownFileIfExists}
+ />
+ );
+ }
+
+ if (_.isUndefined(docSection)) {
+ return null;
+ }
+
+ const sortedProperties = _.sortBy(docSection.properties, 'name');
+ const propertyDefs = _.map(sortedProperties, this.renderProperty.bind(this));
+
+ const sortedMethods = _.sortBy(docSection.methods, 'name');
+ const methodDefs = _.map(sortedMethods, method => {
+ const isConstructor = false;
+ return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName);
+ });
+
+ const sortedEvents = _.sortBy(docSection.events, 'name');
+ const eventDefs = _.map(sortedEvents, (event: Event, i: number) => {
+ return (
+ <EventDefinition
+ key={`event-${event.name}-${i}`}
+ event={event}
+ />
+ );
+ });
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py2 pr3 md-pl2 sm-pl3"
+ >
+ <div className="flex">
+ <div style={{marginRight: 7}}>
+ <SectionHeader sectionName={sectionName} />
+ </div>
+ {this.renderNetworkBadges(sectionName)}
+ </div>
+ {docSection.comment &&
+ <Comment
+ comment={docSection.comment}
+ />
+ }
+ {docSection.constructors.length > 0 &&
+ <div>
+ <h2 className="thin">Constructor</h2>
+ {this.renderConstructors(docSection.constructors, typeDefinitionByName)}
+ </div>
+ }
+ {docSection.properties.length > 0 &&
+ <div>
+ <h2 className="thin">Properties</h2>
+ <div>{propertyDefs}</div>
+ </div>
+ }
+ {docSection.methods.length > 0 &&
+ <div>
+ <h2 className="thin">Methods</h2>
+ <div>{methodDefs}</div>
+ </div>
+ }
+ {docSection.events.length > 0 &&
+ <div>
+ <h2 className="thin">Events</h2>
+ <div>{eventDefs}</div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderNetworkBadges(sectionName: string) {
+ const networkToAddressByContractName = constants.contractAddresses[this.props.docsVersion];
+ const badges = _.map(networkToAddressByContractName,
+ (addressByContractName: AddressByContractName, networkName: string) => {
+ const contractAddress = addressByContractName[sectionName];
+ const linkIfExists = utils.getEtherScanLinkIfExists(
+ contractAddress, constants.networkIdByName[networkName], EtherscanLinkSuffixes.address,
+ );
+ return (
+ <a
+ key={`badge-${networkName}-${sectionName}`}
+ href={linkIfExists}
+ target="_blank"
+ style={{color: 'white', textDecoration: 'none'}}
+ >
+ <Badge
+ title={networkName}
+ backgroundColor={networkNameToColor[networkName]}
+ />
+ </a>
+ );
+ });
+ return badges;
+ }
+ private renderConstructors(constructors: SolidityMethod[],
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ const constructorDefs = _.map(constructors, constructor => {
+ return this.renderMethodBlocks(
+ constructor, SmartContractsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName,
+ );
+ });
+ return (
+ <div>
+ {constructorDefs}
+ </div>
+ );
+ }
+ private renderProperty(property: Property): React.ReactNode {
+ return (
+ <div
+ key={`property-${property.name}-${property.type.name}`}
+ className="pb3"
+ >
+ <code className="hljs">
+ {property.name}: <Type type={property.type} />
+ </code>
+ {property.source &&
+ <SourceLink
+ version={this.props.docsVersion}
+ source={property.source}
+ />
+ }
+ {property.comment &&
+ <Comment
+ comment={property.comment}
+ className="py2"
+ />
+ }
+ </div>
+ );
+ }
+ private renderMethodBlocks(method: SolidityMethod, sectionName: string, isConstructor: boolean,
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ return (
+ <MethodBlock
+ key={`method-${method.name}`}
+ method={method}
+ typeDefinitionByName={typeDefinitionByName}
+ libraryVersion={this.props.docsVersion}
+ />
+ );
+ }
+ private scrollToHash(): void {
+ const hashWithPrefix = this.props.location.hash;
+ let hash = hashWithPrefix.slice(1);
+ if (_.isEmpty(hash)) {
+ hash = 'smartContractsDocs'; // scroll to the top
+ }
+
+ scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
+ }
+ private getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
+ const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
+ if (_.isUndefined(docAgnosticFormat)) {
+ return menuSubsectionsBySection;
+ }
+
+ const docSections = _.keys(SmartContractsDocSections);
+ _.each(docSections, sectionName => {
+ const docSection = docAgnosticFormat[sectionName];
+ if (_.isUndefined(docSection)) {
+ return; // no-op
+ }
+
+ if (sectionName === SmartContractsDocSections.types) {
+ const sortedTypesNames = _.sortBy(docSection.types, 'name');
+ const typeNames = _.map(sortedTypesNames, t => t.name);
+ menuSubsectionsBySection[sectionName] = typeNames;
+ } else {
+ const sortedEventNames = _.sortBy(docSection.events, 'name');
+ const eventNames = _.map(sortedEventNames, m => m.name);
+ const sortedMethodNames = _.sortBy(docSection.methods, 'name');
+ const methodNames = _.map(sortedMethodNames, m => m.name);
+ menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames];
+ }
+ });
+ return menuSubsectionsBySection;
+ }
+ private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> {
+ const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT);
+ const versions = _.keys(versionToFileName);
+ this.props.dispatcher.updateAvailableDocVersions(versions);
+ const sortedVersions = semverSort.desc(versions);
+ const latestVersion = sortedVersions[0];
+
+ let versionToFetch = latestVersion;
+ if (!_.isUndefined(preferredVersionIfExists)) {
+ const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists];
+ if (!_.isUndefined(preferredVersionFileNameIfExists)) {
+ versionToFetch = preferredVersionIfExists;
+ }
+ }
+ this.props.dispatcher.updateCurrentDocsVersion(versionToFetch);
+
+ const versionFileNameToFetch = versionToFileName[versionToFetch];
+ const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT);
+ const docAgnosticFormat = doxityUtils.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj);
+
+ this.setState({
+ docAgnosticFormat,
+ }, () => {
+ this.scrollToHash();
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx
new file mode 100644
index 000000000..2fb69e2f0
--- /dev/null
+++ b/packages/website/ts/pages/documentation/source_link.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import {colors} from 'material-ui/styles';
+import {Source} from 'ts/types';
+import {constants} from 'ts/utils/constants';
+
+interface SourceLinkProps {
+ source: Source;
+ version: string;
+}
+
+export function SourceLink(props: SourceLinkProps) {
+ const source = props.source;
+ const githubUrl = constants.GITHUB_0X_JS_URL;
+ const sourceCodeUrl = `${githubUrl}/blob/v${props.version}/${source.fileName}#L${source.line}`;
+ return (
+ <div className="pt2" style={{fontSize: 14}}>
+ <a
+ href={sourceCodeUrl}
+ target="_blank"
+ className="underline"
+ style={{color: colors.grey500}}
+ >
+ Source
+ </a>
+ </div>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx
new file mode 100644
index 000000000..af18f97c2
--- /dev/null
+++ b/packages/website/ts/pages/documentation/type.tsx
@@ -0,0 +1,187 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Link as ScrollLink} from 'react-scroll';
+import * as ReactTooltip from 'react-tooltip';
+import {colors} from 'material-ui/styles';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+import {constants} from 'ts/utils/constants';
+import {Type as TypeDef, TypeDocTypes, TypeDefinitionByName} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {TypeDefinition} from 'ts/pages/documentation/type_definition';
+
+const BUILT_IN_TYPE_COLOR = '#e69d00';
+const STRING_LITERAL_COLOR = '#4da24b';
+
+// Some types reference other libraries. For these types, we want to link the user to the relevant documentation.
+const typeToUrl: {[typeName: string]: string} = {
+ Web3: constants.WEB3_DOCS_URL,
+ Provider: constants.WEB3_PROVIDER_DOCS_URL,
+ BigNumber: constants.BIGNUMBERJS_GITHUB_URL,
+};
+
+const typeToSection: {[typeName: string]: string} = {
+ ExchangeWrapper: 'exchange',
+ TokenWrapper: 'token',
+ TokenRegistryWrapper: 'tokenRegistry',
+ EtherTokenWrapper: 'etherToken',
+ ProxyWrapper: 'proxy',
+ TokenTransferProxyWrapper: 'proxy',
+};
+
+interface TypeProps {
+ type: TypeDef;
+ typeDefinitionByName?: TypeDefinitionByName;
+}
+
+// The return type needs to be `any` here so that we can recursively define <Type /> components within
+// <Type /> components (e.g when rendering the union type).
+export function Type(props: TypeProps): any {
+ const type = props.type;
+ const isIntrinsic = type.typeDocType === TypeDocTypes.Intrinsic;
+ const isReference = type.typeDocType === TypeDocTypes.Reference;
+ const isArray = type.typeDocType === TypeDocTypes.Array;
+ const isStringLiteral = type.typeDocType === TypeDocTypes.StringLiteral;
+ let typeNameColor = 'inherit';
+ let typeName: string|React.ReactNode;
+ let typeArgs: React.ReactNode[] = [];
+ switch (type.typeDocType) {
+ case TypeDocTypes.Intrinsic:
+ case TypeDocTypes.Unknown:
+ typeName = type.name;
+ typeNameColor = BUILT_IN_TYPE_COLOR;
+ break;
+
+ case TypeDocTypes.Reference:
+ typeName = type.name;
+ typeArgs = _.map(type.typeArguments, (arg: TypeDef) => {
+ if (arg.typeDocType === TypeDocTypes.Array) {
+ const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`;
+ return (
+ <span>
+ <Type
+ key={key}
+ type={arg.elementType}
+ typeDefinitionByName={props.typeDefinitionByName}
+ />[]
+ </span>
+ );
+ } else {
+ const subType = (
+ <Type
+ key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`}
+ type={arg}
+ typeDefinitionByName={props.typeDefinitionByName}
+ />
+ );
+ return subType;
+ }
+ });
+ break;
+
+ case TypeDocTypes.StringLiteral:
+ typeName = `'${type.value}'`;
+ typeNameColor = STRING_LITERAL_COLOR;
+ break;
+
+ case TypeDocTypes.Array:
+ typeName = type.elementType.name;
+ break;
+
+ case TypeDocTypes.Union:
+ const unionTypes = _.map(type.types, t => {
+ return (
+ <Type
+ key={`type-${t.name}-${t.value}-${t.typeDocType}`}
+ type={t}
+ typeDefinitionByName={props.typeDefinitionByName}
+ />
+ );
+ });
+ typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, '|', curr];
+ });
+ break;
+
+ case TypeDocTypes.TypeParameter:
+ typeName = type.name;
+ break;
+
+ default:
+ throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType);
+ }
+ // HACK: Normalize BigNumber to simply BigNumber. For some reason the type
+ // name is unpredictably one or the other.
+ if (typeName === 'BigNumber') {
+ typeName = 'BigNumber';
+ }
+ const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => {
+ return [prev, ', ', curr];
+ });
+
+ const typeNameUrlIfExists = typeToUrl[(typeName as string)];
+ const sectionNameIfExists = typeToSection[(typeName as string)];
+ if (!_.isUndefined(typeNameUrlIfExists)) {
+ typeName = (
+ <a
+ href={typeNameUrlIfExists}
+ target="_blank"
+ className="text-decoration-none"
+ style={{color: colors.lightBlueA700}}
+ >
+ {typeName}
+ </a>
+ );
+ } else if ((isReference || isArray) &&
+ (typeDocUtils.isPublicType(typeName as string) ||
+ !_.isUndefined(sectionNameIfExists))) {
+ const id = Math.random().toString();
+ const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) ? typeName : sectionNameIfExists;
+ let typeDefinition;
+ if (props.typeDefinitionByName) {
+ typeDefinition = props.typeDefinitionByName[typeName as string];
+ }
+ typeName = (
+ <ScrollLink
+ to={typeDefinitionAnchorId}
+ offset={0}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ?
+ <span
+ onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)}
+ style={{color: colors.lightBlueA700, cursor: 'pointer'}}
+ >
+ {typeName}
+ </span> :
+ <span
+ data-tip={true}
+ data-for={id}
+ onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)}
+ style={{color: colors.lightBlueA700, cursor: 'pointer', display: 'inline-block'}}
+ >
+ {typeName}
+ <ReactTooltip
+ type="light"
+ effect="solid"
+ id={id}
+ className="typeTooltip"
+ >
+ <TypeDefinition customType={typeDefinition} shouldAddId={false} />
+ </ReactTooltip>
+ </span>
+ }
+ </ScrollLink>
+ );
+ }
+ return (
+ <span>
+ <span style={{color: typeNameColor}}>{typeName}</span>
+ {isArray && '[]'}{!_.isEmpty(typeArgs) &&
+ <span>
+ {'<'}{commaSeparatedTypeArgs}{'>'}
+ </span>
+ }
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx
new file mode 100644
index 000000000..bcb07be8e
--- /dev/null
+++ b/packages/website/ts/pages/documentation/type_definition.tsx
@@ -0,0 +1,135 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {constants} from 'ts/utils/constants';
+import {utils} from 'ts/utils/utils';
+import {KindString, CustomType, TypeDocTypes, CustomTypeChild, HeaderSizes} from 'ts/types';
+import {Type} from 'ts/pages/documentation/type';
+import {Interface} from 'ts/pages/documentation/interface';
+import {CustomEnum} from 'ts/pages/documentation/custom_enum';
+import {Enum} from 'ts/pages/documentation/enum';
+import {MethodSignature} from 'ts/pages/documentation/method_signature';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {Comment} from 'ts/pages/documentation/comment';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+
+const KEYWORD_COLOR = '#a81ca6';
+
+interface TypeDefinitionProps {
+ customType: CustomType;
+ shouldAddId?: boolean;
+}
+
+interface TypeDefinitionState {
+ shouldShowAnchor: boolean;
+}
+
+export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDefinitionState> {
+ public static defaultProps: Partial<TypeDefinitionProps> = {
+ shouldAddId: true,
+ };
+ constructor(props: TypeDefinitionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const customType = this.props.customType;
+ if (!typeDocUtils.isPublicType(customType.name)) {
+ return null; // no-op
+ }
+
+ let typePrefix: string;
+ let codeSnippet: React.ReactNode;
+ switch (customType.kindString) {
+ case KindString.Interface:
+ typePrefix = 'Interface';
+ codeSnippet = (
+ <Interface
+ type={customType}
+ />
+ );
+ break;
+
+ case KindString.Variable:
+ typePrefix = 'Enum';
+ codeSnippet = (
+ <CustomEnum
+ type={customType}
+ />
+ );
+ break;
+
+ case KindString.Enumeration:
+ typePrefix = 'Enum';
+ const enumValues = _.map(customType.children, (c: CustomTypeChild) => {
+ return {
+ name: c.name,
+ defaultValue: c.defaultValue,
+ };
+ });
+ codeSnippet = (
+ <Enum
+ values={enumValues}
+ />
+ );
+ break;
+
+ case KindString['Type alias']:
+ typePrefix = 'Type Alias';
+ codeSnippet = (
+ <span>
+ <span style={{color: KEYWORD_COLOR}}>type</span> {customType.name} ={' '}
+ {customType.type.typeDocType !== TypeDocTypes.Reflection ?
+ <Type type={customType.type} /> :
+ <MethodSignature
+ method={customType.type.method}
+ shouldHideMethodName={true}
+ shouldUseArrowSyntax={true}
+ />
+ }
+ </span>
+ );
+ break;
+
+ default:
+ throw utils.spawnSwitchErr('type.kindString', customType.kindString);
+ }
+
+ const typeDefinitionAnchorId = customType.name;
+ return (
+ <div
+ id={this.props.shouldAddId ? typeDefinitionAnchorId : ''}
+ className="pb2"
+ style={{overflow: 'hidden', width: '100%'}}
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <AnchorTitle
+ headerSize={HeaderSizes.H3}
+ title={`${typePrefix} ${customType.name}`}
+ id={this.props.shouldAddId ? typeDefinitionAnchorId : ''}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ <div style={{fontSize: 16}}>
+ <pre>
+ <code className="hljs">
+ {codeSnippet}
+ </code>
+ </pre>
+ </div>
+ {customType.comment &&
+ <Comment
+ comment={customType.comment}
+ className="py2"
+ />
+ }
+ </div>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx
new file mode 100644
index 000000000..c26fb7d0a
--- /dev/null
+++ b/packages/website/ts/pages/documentation/zero_ex_js_documentation.tsx
@@ -0,0 +1,340 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import DocumentTitle = require('react-document-title');
+import findVersions = require('find-versions');
+import semverSort = require('semver-sort');
+import {colors} from 'material-ui/styles';
+import MenuItem from 'material-ui/MenuItem';
+import CircularProgress from 'material-ui/CircularProgress';
+import Paper from 'material-ui/Paper';
+import {
+ Link as ScrollLink,
+ Element as ScrollElement,
+ scroller,
+} from 'react-scroll';
+import {Dispatcher} from 'ts/redux/dispatcher';
+import {
+ KindString,
+ TypeDocNode,
+ ZeroExJsDocSections,
+ Styles,
+ ScreenWidths,
+ TypeDefinitionByName,
+ DocAgnosticFormat,
+ TypescriptMethod,
+ Property,
+ CustomType,
+ Docs,
+} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {utils} from 'ts/utils/utils';
+import {docUtils} from 'ts/utils/doc_utils';
+import {constants} from 'ts/utils/constants';
+import {Loading} from 'ts/components/ui/loading';
+import {MethodBlock} from 'ts/pages/documentation/method_block';
+import {SourceLink} from 'ts/pages/documentation/source_link';
+import {Type} from 'ts/pages/documentation/type';
+import {TypeDefinition} from 'ts/pages/documentation/type_definition';
+import {MarkdownSection} from 'ts/pages/shared/markdown_section';
+import {Comment} from 'ts/pages/documentation/comment';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {SectionHeader} from 'ts/pages/shared/section_header';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+/* tslint:disable:no-var-requires */
+const IntroMarkdown = require('md/docs/0xjs/introduction');
+const InstallationMarkdown = require('md/docs/0xjs/installation');
+const AsyncMarkdown = require('md/docs/0xjs/async');
+const ErrorsMarkdown = require('md/docs/0xjs/errors');
+const versioningMarkdown = require('md/docs/0xjs/versioning');
+/* tslint:enable:no-var-requires */
+
+const SCROLL_TO_TIMEOUT = 500;
+const DOC_JSON_ROOT = constants.S3_0XJS_DOCUMENTATION_JSON_ROOT;
+
+const sectionNameToMarkdown = {
+ [ZeroExJsDocSections.introduction]: IntroMarkdown,
+ [ZeroExJsDocSections.installation]: InstallationMarkdown,
+ [ZeroExJsDocSections.async]: AsyncMarkdown,
+ [ZeroExJsDocSections.errors]: ErrorsMarkdown,
+ [ZeroExJsDocSections.versioning]: versioningMarkdown,
+};
+
+export interface ZeroExJSDocumentationPassedProps {
+ source: string;
+ location: Location;
+}
+
+export interface ZeroExJSDocumentationAllProps {
+ source: string;
+ location: Location;
+ dispatcher: Dispatcher;
+ docsVersion: string;
+ availableDocVersions: string[];
+}
+
+interface ZeroExJSDocumentationState {
+ docAgnosticFormat?: DocAgnosticFormat;
+}
+
+const styles: Styles = {
+ mainContainers: {
+ position: 'absolute',
+ top: 60,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ overflowZ: 'hidden',
+ overflowY: 'scroll',
+ minHeight: 'calc(100vh - 60px)',
+ WebkitOverflowScrolling: 'touch',
+ },
+ menuContainer: {
+ borderColor: colors.grey300,
+ maxWidth: 330,
+ marginLeft: 20,
+ },
+};
+
+export class ZeroExJSDocumentation extends React.Component<ZeroExJSDocumentationAllProps, ZeroExJSDocumentationState> {
+ constructor(props: ZeroExJSDocumentationAllProps) {
+ super(props);
+ this.state = {
+ docAgnosticFormat: undefined,
+ };
+ }
+ public componentWillMount() {
+ const pathName = this.props.location.pathname;
+ const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1);
+ const versions = findVersions(lastSegment);
+ const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined;
+ this.fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
+ }
+ public render() {
+ const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat)
+ ? {}
+ : typeDocUtils.getMenuSubsectionsBySection(this.state.docAgnosticFormat);
+ return (
+ <div>
+ <DocumentTitle title="0x.js Documentation"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ docsVersion={this.props.docsVersion}
+ availableDocVersions={this.props.availableDocVersions}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ shouldFullWidth={true}
+ doc={Docs.ZeroExJs}
+ />
+ {_.isUndefined(this.state.docAgnosticFormat) ?
+ <div
+ className="col col-12"
+ style={styles.mainContainers}
+ >
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{paddingBottom: 11}}>Loading documentation...</div>
+ </div>
+ </div> :
+ <div
+ className="mx-auto flex"
+ style={{color: colors.grey800, height: 43}}
+ >
+ <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
+ <div
+ className="border-right absolute"
+ style={{...styles.menuContainer, ...styles.mainContainers}}
+ >
+ <NestedSidebarMenu
+ selectedVersion={this.props.docsVersion}
+ versions={this.props.availableDocVersions}
+ topLevelMenu={typeDocUtils.getFinal0xjsMenu(this.props.docsVersion)}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ doc={Docs.ZeroExJs}
+ />
+ </div>
+ </div>
+ <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
+ <div
+ id="documentation"
+ style={styles.mainContainers}
+ className="absolute"
+ >
+ <div id="zeroExJSDocs" />
+ <h1 className="md-pl2 sm-pl3">
+ <a href={constants.GITHUB_0X_JS_URL} target="_blank">
+ 0x.js
+ </a>
+ </h1>
+ {this.renderDocumentation()}
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderDocumentation(): React.ReactNode {
+ const typeDocSection = this.state.docAgnosticFormat[ZeroExJsDocSections.types];
+ const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name');
+
+ const subMenus = _.values(constants.menu0xjs);
+ const orderedSectionNames = _.flatten(subMenus);
+ const sections = _.map(orderedSectionNames, this.renderSection.bind(this, typeDefinitionByName));
+
+ return sections;
+ }
+ private renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode {
+ const docSection = this.state.docAgnosticFormat[sectionName];
+
+ const markdownFileIfExists = sectionNameToMarkdown[sectionName];
+ if (!_.isUndefined(markdownFileIfExists)) {
+ return (
+ <MarkdownSection
+ key={`markdown-section-${sectionName}`}
+ sectionName={sectionName}
+ markdownContent={markdownFileIfExists}
+ />
+ );
+ }
+
+ if (_.isUndefined(docSection)) {
+ return null;
+ }
+
+ const typeDefs = _.map(docSection.types, customType => {
+ return (
+ <TypeDefinition
+ key={`type-${customType.name}`}
+ customType={customType}
+ />
+ );
+ });
+ const propertyDefs = _.map(docSection.properties, this.renderProperty.bind(this));
+ const methodDefs = _.map(docSection.methods, method => {
+ const isConstructor = false;
+ return this.renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName);
+ });
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py2 pr3 md-pl2 sm-pl3"
+ >
+ <SectionHeader sectionName={sectionName} />
+ <Comment
+ comment={docSection.comment}
+ />
+ {sectionName === ZeroExJsDocSections.zeroEx && docSection.constructors.length > 0 &&
+ <div>
+ <h2 className="thin">Constructor</h2>
+ {this.renderZeroExConstructors(docSection.constructors, typeDefinitionByName)}
+ </div>
+ }
+ {docSection.properties.length > 0 &&
+ <div>
+ <h2 className="thin">Properties</h2>
+ <div>{propertyDefs}</div>
+ </div>
+ }
+ {docSection.methods.length > 0 &&
+ <div>
+ <h2 className="thin">Methods</h2>
+ <div>{methodDefs}</div>
+ </div>
+ }
+ {typeDefs.length > 0 &&
+ <div>
+ <div>{typeDefs}</div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderZeroExConstructors(constructors: TypescriptMethod[],
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ const constructorDefs = _.map(constructors, constructor => {
+ return this.renderMethodBlocks(
+ constructor, ZeroExJsDocSections.zeroEx, constructor.isConstructor, typeDefinitionByName,
+ );
+ });
+ return (
+ <div>
+ {constructorDefs}
+ </div>
+ );
+ }
+ private renderProperty(property: Property): React.ReactNode {
+ return (
+ <div
+ key={`property-${property.name}-${property.type.name}`}
+ className="pb3"
+ >
+ <code className="hljs">
+ {property.name}: <Type type={property.type} />
+ </code>
+ <SourceLink
+ version={this.props.docsVersion}
+ source={property.source}
+ />
+ {property.comment &&
+ <Comment
+ comment={property.comment}
+ className="py2"
+ />
+ }
+ </div>
+ );
+ }
+ private renderMethodBlocks(method: TypescriptMethod, sectionName: string, isConstructor: boolean,
+ typeDefinitionByName: TypeDefinitionByName): React.ReactNode {
+ return (
+ <MethodBlock
+ key={`method-${method.name}-${!_.isUndefined(method.source) ? method.source.line : ''}`}
+ method={method}
+ typeDefinitionByName={typeDefinitionByName}
+ libraryVersion={this.props.docsVersion}
+ />
+ );
+ }
+ private scrollToHash(): void {
+ const hashWithPrefix = this.props.location.hash;
+ let hash = hashWithPrefix.slice(1);
+ if (_.isEmpty(hash)) {
+ hash = 'zeroExJSDocs'; // scroll to the top
+ }
+
+ scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
+ }
+ private async fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> {
+ const versionToFileName = await docUtils.getVersionToFileNameAsync(DOC_JSON_ROOT);
+ const versions = _.keys(versionToFileName);
+ this.props.dispatcher.updateAvailableDocVersions(versions);
+ const sortedVersions = semverSort.desc(versions);
+ const latestVersion = sortedVersions[0];
+
+ let versionToFetch = latestVersion;
+ if (!_.isUndefined(preferredVersionIfExists)) {
+ const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists];
+ if (!_.isUndefined(preferredVersionFileNameIfExists)) {
+ versionToFetch = preferredVersionIfExists;
+ }
+ }
+ this.props.dispatcher.updateCurrentDocsVersion(versionToFetch);
+
+ const versionFileNameToFetch = versionToFileName[versionToFetch];
+ const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, DOC_JSON_ROOT);
+ const docAgnosticFormat = typeDocUtils.convertToDocAgnosticFormat((versionDocObj as TypeDocNode));
+
+ this.setState({
+ docAgnosticFormat,
+ }, () => {
+ this.scrollToHash();
+ });
+ }
+}
diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx
new file mode 100644
index 000000000..3c65d1042
--- /dev/null
+++ b/packages/website/ts/pages/faq/faq.tsx
@@ -0,0 +1,497 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {Styles, FAQSection, FAQQuestion, WebsitePaths} from 'ts/types';
+import {Link} from 'react-router-dom';
+import {Footer} from 'ts/components/footer';
+import {TopBar} from 'ts/components/top_bar';
+import {Question} from 'ts/pages/faq/question';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+
+export interface FAQProps {
+ source: string;
+ location: Location;
+}
+
+interface FAQState {}
+
+const styles: Styles = {
+ thin: {
+ fontWeight: 100,
+ },
+};
+
+const sections: FAQSection[] = [
+ {
+ name: '0x Protocol',
+ questions: [
+ {
+ prompt: 'What is 0x?',
+ answer: (
+ <div>
+ At its core, 0x is an open and non-rent seeking protocol that facilitates trustless,
+ low friction exchange of Ethereum-based assets. Developers can use 0x as a platform
+ to build exchange applications on top of{' '}
+ (<a href={`${configs.BASE_URL}${WebsitePaths.ZeroExJs}#introduction`} target="blank">0x.js</a>
+ {' '}is a Javascript library for interacting with the 0x protocol). For end users, 0x will be
+ the infrastructure of a wide variety of user-facing applications i.e.{' '}
+ <a href={`${configs.BASE_URL}${WebsitePaths.Portal}`} target="blank">0x Portal</a>,
+ a decentralized application that facilitates trustless trading of Ethereum-based tokens
+ between known counterparties.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What problem does 0x solve?',
+ answer: (
+ <div>
+ In the two years since the Ethereum blockchain’s genesis block, numerous decentralized
+ applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange.
+ Rapid iteration and a lack of best practices have left the blockchain scattered with
+ proprietary and application-specific implementations. As a result, end users are
+ exposed to numerous smart contracts of varying quality and security, with unique
+ configuration processes and learning curves, all of which implement the same
+ functionality. This approach imposes unnecessary costs on the network by fragmenting
+ end users according to the particular dApp each user happens to be using, eliminating
+ valuable network effects around liquidity. 0x is the solution to this problem by
+ acting as modular, unopinionated building blocks that may be assembled and reconfigured.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?',
+ answer: (
+ <div>
+ <ul>
+ <li>
+ 0x is a protocol for exchange, not a user-facing exchange application.
+ </li>
+ <li>
+ 0x is decentralized and trustless; there is no central party which can be
+ hacked, run away with customer funds or be subjected to government regulations.
+ Hacks of Mt. Gox, Shapeshift and Bitfinex have demonstrated that these types of
+ systemic risks are palpable.
+ </li>
+ <li>
+ Rather than a proprietary system that exists to extract rent for its owners,
+ 0x is public infrastructure that is funded by a globally distributed community
+ of stakeholders. While the protocol is free to use, it enables for-profit
+ user-facing exchange applications to be built on top of the protocol.
+ </li>
+ </ul>
+ </div>
+ ),
+ },
+ {
+ prompt: 'If 0x protocol is free to use, where do transaction fees come in?',
+ answer: (
+ <div>
+ 0x protocol uses off-chain order books to massively reduce friction costs for
+ market makers and ensure that the blockchain is only used for trade settlement.
+ Hosting and maintaining an off-chain order book is a service; to incent “Relayers”
+ to provide this service they must be able to charge transaction fees on trading
+ activity. Relayers are free to set their transaction fees to any value they desire.
+ We expect Relayers to be highly competitive and transaction fees to approach an
+ efficient economic equilibrium over time.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What are the differences between 0x protocol and state channels?',
+ answer: (
+ <div>
+ <div>
+ Participants in a state channel pass cryptographically signed messages back and
+ forth, accumulating intermediate state changes without publishing them to the
+ canonical chain until the channel is closed. State channels are ideal for “bar tab”
+ applications where numerous intermediate state changes may be accumulated off-chain
+ before being settled by a final on-chain transaction (i.e. day trading, poker,
+ turn-based games).
+ </div>
+ <ul>
+ <li>
+ While state channels drastically reduce the number of on-chain transactions
+ for specific use cases, numerous on-chain transactions and a security deposit
+ are required to open and safely close a state channel making them less efficient
+ than 0x for executing one-time trades.
+ </li>
+ <li>
+ State channels are isolated from the Ethereum blockchain meaning that
+ they cannot interact with smart contracts. 0x is designed to be integrated
+ directly into smart contracts so trades can be executed programmatically
+ in a single line of Solidity code.
+ </li>
+ </ul>
+ </div>
+ ),
+ },
+ {
+ prompt: 'What types of digital assets are supported by 0x?',
+ answer: (
+ <div>
+ 0x supports all Ethereum-based assets that adhere to the ERC20 token standard.
+ There are many ERC20 tokens, worth a combined $2.2B, and more tokens are created
+ each month. We believe that, by 2020, thousands of assets will be tokenized and
+ moved onto the Ethereum blockchain including traditional securities such as equities,
+ bonds and derivatives, fiat currencies and scarce digital goods such as video game
+ items. In the future, cross-blockchain solutions such as{' '}
+ <a href="https://cosmos.network/" target="_blank">Cosmos</a> and{' '}
+ <a href="http://polkadot.io/" target="_blank">Polkadot</a> will allow cryptocurrencies
+ to freely move between blockchains and, naturally, currencies such as Bitcoin will
+ end up being represented as ERC20 tokens on the Ethereum blockchain.
+ </div>
+ ),
+ },
+ {
+ prompt: '0x is open source: what prevents someone from forking the protocol?',
+ answer: (
+ <div>
+ Ethereum and Bitcoin are both open source protocols. Each protocol has been forked,
+ but the resulting clone networks have seen little adoption (as measured by transaction
+ count or market cap). This is because users have little to no incentive to switch
+ over to a clone network if the original has initial network effects and a talented
+ developer team behind it.
+ An exception is in the case that a protocol includes a controversial feature such as
+ a method of rent extraction or a monetary policy that favors one group of users over
+ another (Zcash developer subsidy - for better or worse - resulted in Zclassic).
+ Perceived inequality can provide a strong enough incentive that users will fork the
+ original protocol’s codebase and spin up a new network that eliminates the controversial
+ feature. In the case of 0x, there is no rent extraction and no users are given
+ special permissions.
+
+ 0x protocol is upgradable. Cutting-edge technical capabilities can be integrated
+ into 0x via decentralized governance (see section below), eliminating incentives
+ to fork off of the original protocol and sacrifice the network effects surrounding
+ liquidity that result from the shared protocol and settlement layer.
+ </div>
+ ),
+ },
+ ],
+ },
+ {
+ name: '0x Token (ZRX)',
+ questions: [
+ {
+ prompt: 'Explain how the 0x protocol token (zrx) works.',
+ answer: (
+ <div>
+ <div>
+ 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '}
+ <a href="https://en.wikipedia.org/wiki/Coordination_game" target="_blank">
+ coordination problem
+ </a> and drive network effects around liquidity, creating a feedback loop
+ where early adopters of the protocol benefit from wider adoption and 2) to
+ be used for decentralized governance over 0x protocol's update mechanism.
+ In more detail:
+ </div>
+ <ul>
+ <li>
+ ZRX tokens are used by Makers and Takers (market participants that generate
+ and consume orders, respectively) to pay transaction fees to Relayers
+ (entities that host and maintain public order books).
+ </li>
+ <li>
+ ZRX tokens are used for decentralized governance over 0x protocol’s update
+ mechanism which allows its underlying smart contracts to be replaced and
+ improved over time. An update mechanism is needed because 0x is built upon
+ Ethereum’s rapidly evolving technology stack, decentralized governance is
+ needed because 0x protocol’s smart contracts will have access to user funds
+ and numerous dApps will need to plug into 0x smart contracts. Decentralized
+ governance ensures that this update process is secure and minimizes disruption
+ to the network.
+ </li>
+ </ul>
+ </div>
+ ),
+ },
+ {
+ prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?',
+ answer: (
+ <div>
+ 0x protocol’s decentralized update mechanism is analogous to proof-of-stake.
+ To maximize the alignment of stakeholder and end user incentives, the staked
+ asset must provide utility within the protocol.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How will decentralized governance work?',
+ answer: (
+ <div>
+ Decentralized governance is an ongoing focus of research; it will involve token
+ voting with ZRX. Ultimately the solution will maximize security while also maximizing
+ the protocol’s ability to absorb new innovations. Until the governance structure is
+ formalized and encoded within 0x DAO, a multi-sig will be used as a placeholder.
+ </div>
+ ),
+ },
+ ],
+ },
+ {
+ name: 'ZRX Token Launch and Fund Use',
+ questions: [
+ {
+ prompt: 'What is the total supply of ZRX tokens?',
+ answer: (
+ <div>
+ 1,000,000,000 ZRX. Fixed supply.
+ </div>
+ ),
+ },
+ {
+ prompt: 'When is the Token Launch? will there be a pre-sale?',
+ answer: (
+ <div>
+ The token launch will be on August 15th, 2017. There will not be a pre-sale.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What will the token launch proceeds be used for?',
+ answer: (
+ <div>
+ 100% of the proceeds raised in the token launch will be used to fund the development
+ of free and open source software, tools and infrastructure that support the protocol
+ and surrounding ecosystem. Check out our{' '}
+ <a
+ href="https://docs.google.com/document/d/1_RVa-_bkU92fWRsC8eNy4vYjcTt-WC8GtqyyjbTd-oY"
+ target="_blank"
+ >
+ development roadmap
+ </a>.
+ </div>
+ ),
+ },
+ {
+ prompt: 'What will be the initial distribution of ZRX tokens?',
+ answer: (
+ <div>
+ <div className="center" style={{width: '100%'}}>
+ <img
+ style={{width: 350}}
+ src="/images/zrx_pie_chart.png"
+ />
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Token Launch (50%)
+ </div>
+ <div>
+ ZRX is inherently a governance token that plays a critical role in the
+ process of upgrading 0x protocol. We are fully committed to formulating
+ a functional and theoretically sound governance model and we plan to dedicate
+ significant resources to R&D.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Retained by 0x (15%)
+ </div>
+ <div>
+ The 0x core development team will be able to sustain itself for approximately
+ five years using funds raised through the token launch. If 0x protocol
+ proves to be as foundational a technology as we believe it to be, the
+ retained ZRX tokens will allow the 0x core development team to sustain
+ operations beyond the first 5 years.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Developer Fund (15%)
+ </div>
+ <div>
+ The Developer Fund will be used to make targeted capital injections
+ into high potential projects and teams that are attempting to grow
+ the 0x ecosystem, strategic partnerships, hackathon prizes and community
+ development activities.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Founding Team (10%)
+ </div>
+ <div>
+ The founding team’s allocation of ZRX will vest over a traditional 4
+ year vesting schedule with a one year cliff. We believe this should
+ be standard practice for any team that is committed to making their
+ project a long term success.
+ </div>
+ </div>
+ <div className="py1">
+ <div className="bold pb1">
+ Early Backers & Advisors (10%)
+ </div>
+ <div>
+ Our backers and advisors have provided capital, resources and guidance
+ that have allowed us to fill out our team, setup a robust legal entity
+ and build a fully functional product before launching a token. As a result,
+ we have a proven track record and can offer a token that holds genuine utility.
+ </div>
+ </div>
+ </div>
+ ),
+ },
+ {
+ prompt: 'Can I mine ZRX tokens?',
+ answer: (
+ <div>
+ No, the total supply of ZRX tokens is fixed and there is no continuous issuance
+ model. Users that facilitate trading over 0x protocol by operating a Relayer
+ earn transaction fees denominated in ZRX; as more trading activity is generated,
+ more transaction fees are earned.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?',
+ answer: (
+ <div>
+ No, ZRX tokens sold in the token launch will immediately be liquid.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Will there be a lockup period for tokens allocated to the founding team?',
+ answer: (
+ <div>
+ Yes. ZRX tokens allocated to founders, advisors and staff members will be released
+ over a 4 year vesting schedule with a 25% cliff upon completion of the initial
+ token launch and 25% released each subsequent year in monthly installments. Staff
+ members hired after the token launch will have a 4 year vesting schedule with a
+ one year cliff.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Which cryptocurrencies will be accepted in the token launch?',
+ answer: (
+ <div>ETH.</div>
+ ),
+ },
+ {
+ prompt: 'When will 0x be live?',
+ answer: (
+ <div>
+ An alpha version of 0x has been live on our private test network since January
+ 2017. Version 1.0 of 0x protocol will be deployed to the canonical Ethereum
+ blockchain after a round of security audits and prior to the public token launch.
+ 0x will be using the 0x protocol during our token launch.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Where can I find a development roadmap?',
+ answer: (
+ <div>
+ Check it out{' '}
+ <a
+ href="https://drive.google.com/open?id=14IP1N8mt3YdsAoqYTyruMnZswpklUs3THyS1VXx71fo"
+ target="_blank"
+ >
+ here
+ </a>.
+ </div>
+ ),
+ },
+ ],
+ },
+ {
+ name: 'Team',
+ questions: [
+ {
+ prompt: 'Where is 0x based?',
+ answer: (
+ <div>
+ 0x was founded in SF and is driven by a diverse group of contributors.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How can I get involved?',
+ answer: (
+ <div>
+ Join our <a href={constants.ZEROEX_CHAT_URL} target="_blank">Rocket.chat</a>!
+ As an open source project, 0x will rely on a worldwide community of passionate
+ developers to contribute proposals, ideas and code.
+ </div>
+ ),
+ },
+ {
+ prompt: 'Why the name 0x?',
+ answer: (
+ <div>
+ 0x is the prefix for hexadecimal numeric constants including Ethereum addresses.
+ In a more abstract context, as the first open protocol for exchange 0x represents
+ the beginning of the end for the exchange industry’s rent seeking oligopoly:
+ zero exchange.
+ </div>
+ ),
+ },
+ {
+ prompt: 'How do you pronounce 0x?',
+ answer: (
+ <div>
+ We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please.
+ </div>
+ ),
+ },
+ ],
+ },
+];
+
+export class FAQ extends React.Component<FAQProps, FAQState> {
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div>
+ <DocumentTitle title="0x FAQ"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ />
+ <div
+ id="faq"
+ className="mx-auto max-width-4 pt4"
+ style={{color: colors.grey800}}
+ >
+ <h1 className="center" style={{...styles.thin}}>0x FAQ</h1>
+ <div className="sm-px2 md-px2 lg-px0 pb4">
+ {this.renderSections()}
+ </div>
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderSections() {
+ const renderedSections = _.map(sections, (section: FAQSection, i: number) => {
+ const isFirstSection = i === 0;
+ return (
+ <div key={section.name}>
+ <h3>{section.name}</h3>
+ {this.renderQuestions(section.questions, isFirstSection)}
+ </div>
+ );
+ });
+ return renderedSections;
+ }
+ private renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) {
+ const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => {
+ const isFirstQuestion = i === 0;
+ return (
+ <Question
+ key={question.prompt}
+ prompt={question.prompt}
+ answer={question.answer}
+ shouldDisplayExpanded={isFirstSection && isFirstQuestion}
+ />
+ );
+ });
+ return renderedQuestions;
+ }
+}
diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx
new file mode 100644
index 000000000..4ed198b91
--- /dev/null
+++ b/packages/website/ts/pages/faq/question.tsx
@@ -0,0 +1,52 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Card, CardHeader, CardText} from 'material-ui/Card';
+
+export interface QuestionProps {
+ prompt: string;
+ answer: React.ReactNode;
+ shouldDisplayExpanded: boolean;
+}
+
+interface QuestionState {
+ isExpanded: boolean;
+}
+
+export class Question extends React.Component<QuestionProps, QuestionState> {
+ constructor(props: QuestionProps) {
+ super(props);
+ this.state = {
+ isExpanded: props.shouldDisplayExpanded,
+ };
+ }
+ public render() {
+ return (
+ <div
+ className="py1"
+ >
+ <Card
+ initiallyExpanded={this.props.shouldDisplayExpanded}
+ onExpandChange={this.onExchangeChange.bind(this)}
+ >
+ <CardHeader
+ title={this.props.prompt}
+ style={{borderBottom: this.state.isExpanded ? '1px solid rgba(0, 0, 0, 0.19)' : 'none'}}
+ titleStyle={{color: 'rgb(66, 66, 66)'}}
+ actAsExpander={true}
+ showExpandableButton={true}
+ />
+ <CardText expandable={true}>
+ <div style={{lineHeight: 1.4}}>
+ {this.props.answer}
+ </div>
+ </CardText>
+ </Card>
+ </div>
+ );
+ }
+ private onExchangeChange() {
+ this.setState({
+ isExpanded: !this.state.isExpanded,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx
new file mode 100644
index 000000000..32ea86736
--- /dev/null
+++ b/packages/website/ts/pages/landing/landing.tsx
@@ -0,0 +1,843 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import {Link} from 'react-router-dom';
+import RaisedButton from 'material-ui/RaisedButton';
+import {colors} from 'material-ui/styles';
+import {configs} from 'ts/utils/configs';
+import {constants} from 'ts/utils/constants';
+import {Styles, WebsitePaths, ScreenWidths} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {TopBar} from 'ts/components/top_bar';
+import {Footer} from 'ts/components/footer';
+
+interface BoxContent {
+ title: string;
+ description: string;
+ imageUrl: string;
+ classNames: string;
+}
+interface AssetType {
+ title: string;
+ imageUrl: string;
+ style?: React.CSSProperties;
+}
+interface UseCase {
+ imageUrl: string;
+ type: string;
+ description: string;
+ classNames: string;
+ style?: React.CSSProperties;
+ projectIconUrls: string[];
+}
+interface Project {
+ logoFileName: string;
+ projectUrl: string;
+}
+
+const THROTTLE_TIMEOUT = 100;
+const CUSTOM_HERO_BACKGROUND_COLOR = '#404040';
+const CUSTOM_PROJECTS_BACKGROUND_COLOR = '#343333';
+const CUSTOM_WHITE_BACKGROUND = 'rgb(245, 245, 245)';
+const CUSTOM_WHITE_TEXT = '#E4E4E4';
+const CUSTOM_GRAY_TEXT = '#919191';
+
+const boxContents: BoxContent[] = [
+ {
+ title: 'Trustless exchange',
+ description: 'Built on Ethereum\'s distributed network with no centralized \
+ point of failure and no down time, each trade is settled atomically \
+ and without counterparty risk.',
+ imageUrl: '/images/landing/distributed_network.png',
+ classNames: '',
+ },
+ {
+ title: 'Shared liquidity',
+ description: 'By sharing a standard API, relayers can easily aggregate liquidity pools, \
+ creating network effects around liquidity that compound as more relayers come online.',
+ imageUrl: '/images/landing/liquidity.png',
+ classNames: 'mx-auto',
+ },
+ {
+ title: 'Open source',
+ description: '0x is open source, permissionless and free to use. Trade directly with a known \
+ counterparty for free or pay a relayer some ZRX tokens to access their liquidity \
+ pool.',
+ imageUrl: '/images/landing/open_source.png',
+ classNames: 'right',
+ },
+];
+
+const projects: Project[] = [
+ {
+ logoFileName: 'ethfinex-top.png',
+ projectUrl: constants.ETHFINEX_URL,
+ },
+ {
+ logoFileName: 'radar_relay_top.png',
+ projectUrl: constants.RADAR_RELAY_URL,
+ },
+ {
+ logoFileName: 'paradex_top.png',
+ projectUrl: constants.PARADEX_URL,
+ },
+ {
+ logoFileName: 'the_ocean.png',
+ projectUrl: constants.OCEAN_URL,
+ },
+ {
+ logoFileName: 'dydx.png',
+ projectUrl: constants.DYDX_URL,
+ },
+ {
+ logoFileName: 'melonport.png',
+ projectUrl: constants.MELONPORT_URL,
+ },
+ {
+ logoFileName: 'maker.png',
+ projectUrl: constants.MAKER_URL,
+ },
+ {
+ logoFileName: 'dharma.png',
+ projectUrl: constants.DHARMA_URL,
+ },
+ {
+ logoFileName: 'lendroid.png',
+ projectUrl: constants.LENDROID_URL,
+ },
+ {
+ logoFileName: 'district0x.png',
+ projectUrl: constants.DISTRICT_0X_URL,
+ },
+ {
+ logoFileName: 'aragon.png',
+ projectUrl: constants.ARAGON_URL,
+ },
+ {
+ logoFileName: 'blocknet.png',
+ projectUrl: constants.BLOCKNET_URL,
+ },
+ {
+ logoFileName: 'status.png',
+ projectUrl: constants.STATUS_URL,
+ },
+ {
+ logoFileName: 'augur.png',
+ projectUrl: constants.AUGUR_URL,
+ },
+ {
+ logoFileName: 'anx.png',
+ projectUrl: constants.OPEN_ANX_URL,
+ },
+ {
+ logoFileName: 'auctus.png',
+ projectUrl: constants.AUCTUS_URL,
+ },
+];
+
+export interface LandingProps {
+ location: Location;
+}
+
+interface LandingState {
+ screenWidth: ScreenWidths;
+}
+
+export class Landing extends React.Component<LandingProps, LandingState> {
+ private throttledScreenWidthUpdate: () => void;
+ constructor(props: LandingProps) {
+ super(props);
+ this.state = {
+ screenWidth: utils.getScreenWidth(),
+ };
+ this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
+ }
+ public componentDidMount() {
+ window.addEventListener('resize', this.throttledScreenWidthUpdate);
+ window.scrollTo(0, 0);
+ }
+ public componentWillUnmount() {
+ window.removeEventListener('resize', this.throttledScreenWidthUpdate);
+ }
+ public render() {
+ return (
+ <div id="landing" className="clearfix" style={{color: colors.grey800}}>
+ <DocumentTitle title="0x Protocol"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ isNightVersion={true}
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR, position: 'relative'}}
+ />
+ {this.renderHero()}
+ {this.renderProjects()}
+ {this.renderTokenizationSection()}
+ {this.renderProtocolSection()}
+ {this.renderInfoBoxes()}
+ {this.renderBuildingBlocksSection()}
+ {this.renderUseCases()}
+ {this.renderCallToAction()}
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+ private renderHero() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const buttonLabelStyle: React.CSSProperties = {
+ textTransform: 'none',
+ fontSize: isSmallScreen ? 12 : 14,
+ fontWeight: 400,
+ };
+ const lightButtonStyle: React.CSSProperties = {
+ borderRadius: 6,
+ border: '1px solid #D8D8D8',
+ lineHeight: '33px',
+ height: 38,
+ };
+ const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center';
+ return (
+ <div
+ className="clearfix py4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto max-width-4 clearfix"
+ >
+ <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix">
+ <div className="col lg-col-5 md-col-5 col-12 sm-center">
+ <img
+ src="/images/landing/hero_chip_image.png"
+ height={isSmallScreen ? 300 : 395}
+ />
+ </div>
+ <div
+ className={left}
+ style={{color: 'white'}}
+ >
+ <div style={{paddingLeft: isSmallScreen ? 0 : 12}}>
+ <div
+ className="sm-pb2"
+ style={{fontFamily: 'Roboto Mono', fontSize: isSmallScreen ? 26 : 34}}
+ >
+ Powering decentralized exchange
+ </div>
+ <div
+ className="pt2 h5 sm-mx-auto"
+ style={{maxWidth: 446, fontFamily: 'Roboto Mono', lineHeight: 1.7, fontWeight: 300}}
+ >
+ 0x is an open, permissionless protocol allowing for ERC20 tokens to
+ be traded on the Ethereum blockchain.
+ </div>
+ <div className="pt3 clearfix sm-mx-auto" style={{maxWidth: 342}}>
+ <div className="lg-pr2 md-pr2 col col-6 sm-center">
+ <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
+ <RaisedButton
+ style={{borderRadius: 6, minWidth: 157.36}}
+ buttonStyle={{borderRadius: 6}}
+ labelStyle={buttonLabelStyle}
+ label="Build on 0x"
+ onClick={_.noop}
+ />
+ </Link>
+ </div>
+ <div className="col col-6 sm-center">
+ <a
+ href={constants.ZEROEX_CHAT_URL}
+ target="_blank"
+ className="text-decoration-none"
+ >
+ <RaisedButton
+ style={{borderRadius: 6, minWidth: 150}}
+ buttonStyle={lightButtonStyle}
+ labelColor="white"
+ backgroundColor={CUSTOM_HERO_BACKGROUND_COLOR}
+ labelStyle={buttonLabelStyle}
+ label="Join the community"
+ onClick={_.noop}
+ />
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderProjects() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const isMediumScreen = this.state.screenWidth === ScreenWidths.MD;
+ const projectList = _.map(projects, (project: Project, i: number) => {
+ const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - (i % 2);
+ return (
+ <div
+ key={`project-${project.logoFileName}`}
+ className={`col col-${colWidth} center`}
+ >
+ <div>
+ <a
+ href={project.projectUrl}
+ target="_blank"
+ className="text-decoration-none"
+ >
+ <img
+ src={`/images/landing/project_logos/${project.logoFileName}`}
+ height={isSmallScreen ? 60 : 92}
+ />
+ </a>
+ </div>
+ </div>
+ );
+ });
+ const titleStyle: React.CSSProperties = {
+ fontFamily: 'Roboto Mono',
+ color: '#A4A4A4',
+ textTransform: 'uppercase',
+ fontWeight: 300,
+ letterSpacing: 3,
+ };
+ return (
+ <div
+ className="clearfix py4"
+ style={{backgroundColor: CUSTOM_PROJECTS_BACKGROUND_COLOR}}
+ >
+ <div className="mx-auto max-width-4 clearfix sm-px3">
+ <div
+ className="h4 pb3 md-pl3 sm-pl2"
+ style={titleStyle}
+ >
+ Projects building on 0x
+ </div>
+ <div className="clearfix">
+ {projectList}
+ </div>
+ <div
+ className="pt3 mx-auto center"
+ style={{color: CUSTOM_GRAY_TEXT, fontFamily: 'Roboto Mono', maxWidth: 300, fontSize: 14}}
+ >
+ view the{' '}
+ <Link
+ to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_GRAY_TEXT}}
+ >
+ full list
+ </Link>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderTokenizationSection() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div
+ className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2"
+ style={{backgroundColor: CUSTOM_WHITE_BACKGROUND}}
+ >
+ <div className="mx-auto max-width-4 py4 clearfix">
+ {isSmallScreen &&
+ this.renderTokenCloud()
+ }
+ <div className="col lg-col-6 md-col-6 col-12">
+ <div className="mx-auto" style={{maxWidth: 385, paddingTop: 7}}>
+ <div
+ className="lg-h1 md-h1 sm-h2 sm-center sm-pt3"
+ style={{fontFamily: 'Roboto Mono'}}
+ >
+ The world's value is becoming tokenized
+ </div>
+ <div
+ className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center"
+ style={{fontFamily: 'Roboto Mono', lineHeight: 1.7}}
+ >
+ {isSmallScreen ?
+ <span>
+ The Ethereum blockchain is an open, borderless financial system that represents
+ a wide variety of assets as cryptographic tokens. In the future, most digital
+ assets and goods will be tokenized.
+ </span> :
+ <div>
+ <div>
+ The Ethereum blockchain is an open, borderless
+ financial system that represents
+ </div>
+ <div>
+ a wide variety of assets as cryptographic tokens.
+ In the future, most digital assets and goods will be tokenized.
+ </div>
+ </div>
+ }
+ </div>
+ <div className="flex pt1 sm-px3">
+ {this.renderAssetTypes()}
+ </div>
+ </div>
+ </div>
+ {!isSmallScreen &&
+ this.renderTokenCloud()
+ }
+ </div>
+ </div>
+ );
+ }
+ private renderProtocolSection() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div
+ className="clearfix lg-py4 md-py4 sm-pt4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div className="mx-auto max-width-4 lg-py4 md-py4 sm-pt4 clearfix">
+ <div className="col lg-col-6 md-col-6 col-12 sm-center">
+ <img
+ src="/images/landing/relayer_diagram.png"
+ height={isSmallScreen ? 326 : 426}
+ />
+ </div>
+ <div
+ className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto"
+ style={{color: CUSTOM_WHITE_TEXT, paddingTop: 8, maxWidth: isSmallScreen ? 'none' : 445}}
+ >
+ <div
+ className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center"
+ style={{fontFamily: 'Roboto Mono'}}
+ >
+ <div>
+ Off-chain order relay
+ </div>
+ <div>
+ On-chain settlement
+ </div>
+ </div>
+ <div
+ className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto"
+ style={{fontFamily: 'Roboto Mono', lineHeight: 1.7, fontWeight: 300, maxWidth: 445}}
+ >
+ In 0x protocol, orders are transported off-chain, massively reducing gas
+ costs and eliminating blockchain bloat. Relayers help broadcast orders and
+ collect a fee each time they facilitate a trade. Anyone can build a relayer.
+ </div>
+ <div
+ className="pt3 sm-mx-auto sm-px3"
+ style={{color: CUSTOM_GRAY_TEXT, maxWidth: isSmallScreen ? 412 : 'none'}}
+ >
+ <div className="flex" style={{fontSize: 18}}>
+ <div
+ className="lg-h4 md-h4 sm-h5"
+ style={{letterSpacing: isSmallScreen ? 1 : 3, fontFamily: 'Roboto Mono'}}
+ >
+ RELAYERS BUILDING ON 0X
+ </div>
+ <div className="h5" style={{marginLeft: isSmallScreen ? 26 : 49}}>
+ <Link
+ to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_GRAY_TEXT, fontFamily: 'Roboto Mono'}}
+ >
+ view all
+ </Link>
+ </div>
+ </div>
+ <div className="lg-flex md-flex sm-clearfix pt3" style={{opacity: 0.4}}>
+ <div className="col col-4 sm-center">
+ <img
+ src="/images/landing/ethfinex.png"
+ style={{height: isSmallScreen ? 85 : 107}}
+ />
+ </div>
+ <div
+ className="col col-4 center"
+ >
+ <img
+ src="/images/landing/radar_relay.png"
+ style={{height: isSmallScreen ? 85 : 107}}
+ />
+ </div>
+ <div className="col col-4 sm-center" style={{textAlign: 'right'}}>
+ <img
+ src="/images/landing/paradex.png"
+ style={{height: isSmallScreen ? 85 : 107}}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private renderBuildingBlocksSection() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const underlineStyle: React.CSSProperties = {
+ height: isSmallScreen ? 18 : 23,
+ lineHeight: 'none',
+ borderBottom: '2px solid #979797',
+ };
+ const descriptionStyle: React.CSSProperties = {
+ fontFamily: 'Roboto Mono',
+ lineHeight: isSmallScreen ? 1.5 : 2,
+ fontWeight: 300,
+ fontSize: 15,
+ maxWidth: isSmallScreen ? 375 : 'none',
+ };
+ const callToActionStyle: React.CSSProperties = {
+ fontFamily: 'Roboto Mono',
+ fontSize: 15,
+ fontWeight: 300,
+ maxWidth: isSmallScreen ? 375 : 441,
+ };
+ return (
+ <div
+ className="clearfix lg-pt4 md-pt4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div className="mx-auto max-width-4 lg-pt4 md-pt4 lg-mb4 md-mb4 sm-mb2 clearfix">
+ {isSmallScreen &&
+ this.renderBlockChipImage()
+ }
+ <div
+ className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-px3"
+ style={{color: CUSTOM_WHITE_TEXT}}
+ >
+ <div
+ className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center"
+ style={{fontFamily: 'Roboto Mono'}}
+ >
+ A building block for dApps
+ </div>
+ <div
+ className="pb3 pt2 sm-mx-auto sm-center"
+ style={descriptionStyle}
+ >
+ 0x protocol is a pluggable building block for dApps that require exchange functionality.
+ Join the many developers that are already using 0x in their web applications and smart
+ contracts.
+ </div>
+ <div
+ className="sm-mx-auto sm-center"
+ style={callToActionStyle}
+ >
+ Learn how in our{' '}
+ <Link
+ to={WebsitePaths.ZeroExJs}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_WHITE_TEXT, fontFamily: 'Roboto Mono'}}
+ >
+ 0x.js
+ </Link>
+ {' '}and{' '}
+ <Link
+ to={WebsitePaths.SmartContracts}
+ className="text-decoration-none underline"
+ style={{color: CUSTOM_WHITE_TEXT, fontFamily: 'Roboto Mono'}}
+ >
+ smart contract
+ </Link>
+ {' '}docs
+ </div>
+ </div>
+ {!isSmallScreen &&
+ this.renderBlockChipImage()
+ }
+ </div>
+ </div>
+ );
+ }
+ private renderBlockChipImage() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div className="col lg-col-6 md-col-6 col-12 sm-center">
+ <img
+ src="/images/landing/0x_chips.png"
+ height={isSmallScreen ? 240 : 368}
+ />
+ </div>
+ );
+ }
+ private renderTokenCloud() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ return (
+ <div className="col lg-col-6 md-col-6 col-12 center">
+ <img
+ src="/images/landing/tokenized_world.png"
+ height={isSmallScreen ? 280 : 364.5}
+ />
+ </div>
+ );
+ }
+ private renderAssetTypes() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const assetTypes: AssetType[] = [
+ {
+ title: 'Currency',
+ imageUrl: '/images/landing/currency.png',
+ },
+ {
+ title: 'Traditional assets',
+ imageUrl: '/images/landing/stocks.png',
+ style: {paddingLeft: isSmallScreen ? 41 : 56, paddingRight: isSmallScreen ? 41 : 56},
+ },
+ {
+ title: 'Digital goods',
+ imageUrl: '/images/landing/digital_goods.png',
+ },
+ ];
+ const assets = _.map(assetTypes, (assetType: AssetType) => {
+ const style = _.isUndefined(assetType.style) ? {} : assetType.style;
+ return (
+ <div
+ key={`asset-${assetType.title}`}
+ className="center"
+ style={{opacity: 0.8, ...style}}
+ >
+ <div>
+ <img
+ src={assetType.imageUrl}
+ height="80"
+ />
+ </div>
+ <div style={{fontFamily: 'Roboto Mono', fontSize: 13.5, fontWeight: 400, opacity: 0.75}}>
+ {assetType.title}
+ </div>
+ </div>
+ );
+ });
+ return assets;
+ }
+ private renderLink(label: string, path: string, color: string, style?: React.CSSProperties) {
+ return (
+ <div
+ style={{borderBottom: `1px solid ${color}`, paddingBottom: 1, height: 20, lineHeight: 1.7, ...style}}
+ >
+ <Link
+ to={path}
+ className="text-decoration-none"
+ style={{color, fontFamily: 'Roboto Mono'}}
+ >
+ {label}
+ </Link>
+ </div>
+ );
+ }
+ private renderInfoBoxes() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const boxStyle: React.CSSProperties = {
+ maxWidth: 252,
+ height: 386,
+ backgroundColor: '#F9F9F9',
+ borderRadius: 5,
+ padding: '10px 24px 24px',
+ };
+ const boxes = _.map(boxContents, (boxContent: BoxContent) => {
+ return (
+ <div
+ key={`box-${boxContent.title}`}
+ className="col lg-col-4 md-col-4 col-12 sm-pb4"
+ >
+ <div
+ className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`}
+ style={boxStyle}
+ >
+ <div>
+ <img src={boxContent.imageUrl} style={{height: 210}} />
+ </div>
+ <div
+ className="h3"
+ style={{color: 'black', fontFamily: 'Roboto Mono'}}
+ >
+ {boxContent.title}
+ </div>
+ <div
+ className="pt2 pb2"
+ style={{fontFamily: 'Roboto Mono', fontSize: 14}}
+ >
+ {boxContent.description}
+ </div>
+ </div>
+ </div>
+ );
+
+ });
+ return (
+ <div
+ className="clearfix"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto py4 sm-mt2 clearfix"
+ style={{maxWidth: '60em'}}
+ >
+ {boxes}
+ </div>
+ </div>
+ );
+ }
+ private renderUseCases() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const isMediumScreen = this.state.screenWidth === ScreenWidths.MD;
+
+ const useCases: UseCase[] = [
+ {
+ imageUrl: '/images/landing/governance_icon.png',
+ type: 'Decentralized governance',
+ description: 'Decentralized organizations use tokens to represent ownership and \
+ guide their governance logic. 0x allows decentralized organizations \
+ to seamlessly and safely trade ownership for startup capital.',
+ projectIconUrls: ['/images/landing/aragon.png'],
+ classNames: 'lg-px2 md-px2',
+ },
+ {
+ imageUrl: '/images/landing/prediction_market_icon.png',
+ type: 'Prediction markets',
+ description: 'Decentralized prediction market platforms generate sets of tokens that \
+ represent a financial stake in the outcomes of real-world events. 0x allows \
+ these tokens to be instantly tradable.',
+ projectIconUrls: ['/images/landing/augur.png'],
+ classNames: 'lg-px2 md-px2',
+ },
+ {
+ imageUrl: '/images/landing/stable_tokens_icon.png',
+ type: 'Stable tokens',
+ description: 'Novel economic constructs such as stable coins require efficient, liquid \
+ markets to succeed. 0x will facilitate the underlying economic mechanisms \
+ that allow these tokens to remain stable.',
+ projectIconUrls: ['/images/landing/maker.png'],
+ classNames: 'lg-px2 md-px2',
+ },
+ {
+ imageUrl: '/images/landing/loans_icon.png',
+ type: 'Decentralized loans',
+ description: 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \
+ 0x enables an ecosystem of lenders to self-organize and efficiently determine \
+ market prices for all outstanding loans.',
+ projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'],
+ classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6',
+ style: {width: 291, float: 'right', marginTop: !isSmallScreen ? 38 : 0},
+ },
+ {
+ imageUrl: '/images/landing/fund_management_icon.png',
+ type: 'Fund management',
+ description: 'Decentralized fund management limits fund managers to investing in pre-agreed \
+ upon asset classes. Embedding 0x into fund management smart contracts enables \
+ them to enforce these security constraints.',
+ projectIconUrls: ['/images/landing/melonport.png'],
+ classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6',
+ style: {width: 291, marginTop: !isSmallScreen ? 38 : 0},
+ },
+ ];
+
+ const cases = _.map(useCases, (useCase: UseCase) => {
+ const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style;
+ const useCaseBoxStyle = {
+ color: '#A2A2A2',
+ border: '1px solid #565656',
+ borderRadius: 4,
+ maxWidth: isSmallScreen ? 375 : 'none',
+ ...style,
+ };
+ const typeStyle: React.CSSProperties = {
+ color: '#EBEBEB',
+ fontSize: 13,
+ textTransform: 'uppercase',
+ fontFamily: 'Roboto Mono',
+ fontWeight: 300,
+ };
+ return (
+ <div
+ key={`useCase-${useCase.type}`}
+ className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`}
+ >
+ <div
+ className="relative p2 pb2 sm-mx-auto"
+ style={useCaseBoxStyle}
+ >
+ <div
+ className="absolute center"
+ style={{top: -35, width: 'calc(100% - 32px)'}}
+ >
+ <img src={useCase.imageUrl} style={{height: 50}} />
+ </div>
+ <div className="pt2 center" style={typeStyle}>
+ {useCase.type}
+ </div>
+ <div
+ className="pt2"
+ style={{lineHeight: 1.5, fontSize: 14, overflow: 'hidden', height: 104}}
+ >
+ {useCase.description}
+ </div>
+ </div>
+ </div>
+ );
+ });
+ return (
+ <div
+ className="clearfix pb4 lg-pt2 md-pt2 sm-pt4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix"
+ style={{maxWidth: '67em'}}
+ >
+ {cases}
+ </div>
+ </div>
+ );
+ }
+ private renderCallToAction() {
+ const isSmallScreen = this.state.screenWidth === ScreenWidths.SM;
+ const buttonLabelStyle: React.CSSProperties = {
+ textTransform: 'none',
+ fontSize: 15,
+ fontWeight: 400,
+ };
+ const lightButtonStyle: React.CSSProperties = {
+ borderRadius: 6,
+ border: '1px solid #a0a0a0',
+ lineHeight: '33px',
+ height: 49,
+ };
+ const callToActionClassNames = 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \
+ lg-right-align md-right-align sm-center sm-px3 h4';
+ return (
+ <div
+ className="clearfix pb4"
+ style={{backgroundColor: CUSTOM_HERO_BACKGROUND_COLOR}}
+ >
+ <div
+ className="mx-auto max-width-4 pb4 mb3 clearfix"
+ >
+ <div
+ className={callToActionClassNames}
+ style={{fontFamily: 'Roboto Mono', color: 'white', lineHeight: isSmallScreen ? 1.7 : 3}}
+ >
+ Get started on building the decentralized future
+ </div>
+ <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2">
+ <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
+ <RaisedButton
+ style={{borderRadius: 6, minWidth: 150}}
+ buttonStyle={lightButtonStyle}
+ labelColor={colors.white}
+ backgroundColor={CUSTOM_HERO_BACKGROUND_COLOR}
+ labelStyle={buttonLabelStyle}
+ label="Build on 0x"
+ onClick={_.noop}
+ />
+ </Link>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private updateScreenWidth() {
+ const newScreenWidth = utils.getScreenWidth();
+ if (newScreenWidth !== this.state.screenWidth) {
+ this.setState({
+ screenWidth: newScreenWidth,
+ });
+ }
+ }
+}
diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx
new file mode 100644
index 000000000..ddd720c97
--- /dev/null
+++ b/packages/website/ts/pages/not_found.tsx
@@ -0,0 +1,46 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import {Styles} from 'ts/types';
+import {Link} from 'react-router-dom';
+import {Footer} from 'ts/components/footer';
+import {TopBar} from 'ts/components/top_bar';
+
+export interface NotFoundProps {
+ location: Location;
+}
+
+interface NotFoundState {}
+
+const styles: Styles = {
+ thin: {
+ fontWeight: 100,
+ },
+};
+
+export class NotFound extends React.Component<NotFoundProps, NotFoundState> {
+ public render() {
+ return (
+ <div>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ />
+ <div className="mx-auto max-width-4 py4">
+ <div className="center py4">
+ <div className="py4">
+ <div className="py4">
+ <h1 style={{...styles.thin}}>404 Not Found</h1>
+ <div className="py1">
+ <div className="py3">
+ Hm... looks like we couldn't find what you are looking for.
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <Footer location={this.props.location} />
+ </div>
+ );
+ }
+}
diff --git a/packages/website/ts/pages/shared/anchor_title.tsx b/packages/website/ts/pages/shared/anchor_title.tsx
new file mode 100644
index 000000000..dfa9401ae
--- /dev/null
+++ b/packages/website/ts/pages/shared/anchor_title.tsx
@@ -0,0 +1,98 @@
+import * as React from 'react';
+import {Styles, HeaderSizes} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {Link as ScrollLink} from 'react-scroll';
+
+const headerSizeToScrollOffset: {[headerSize: string]: number} = {
+ h2: -20,
+ h3: 0,
+};
+
+interface AnchorTitleProps {
+ title: string|React.ReactNode;
+ id: string;
+ headerSize: HeaderSizes;
+ shouldShowAnchor: boolean;
+}
+
+interface AnchorTitleState {
+ isHovering: boolean;
+}
+
+const styles: Styles = {
+ anchor: {
+ fontSize: 20,
+ transform: 'rotate(45deg)',
+ cursor: 'pointer',
+ },
+ headers: {
+ WebkitMarginStart: 0,
+ WebkitMarginEnd: 0,
+ fontWeight: 'bold',
+ display: 'block',
+ },
+ h1: {
+ fontSize: '1.8em',
+ WebkitMarginBefore: '0.83em',
+ WebkitMarginAfter: '0.83em',
+ },
+ h2: {
+ fontSize: '1.5em',
+ WebkitMarginBefore: '0.83em',
+ WebkitMarginAfter: '0.83em',
+ },
+ h3: {
+ fontSize: '1.17em',
+ WebkitMarginBefore: '1em',
+ WebkitMarginAfter: '1em',
+ },
+};
+
+export class AnchorTitle extends React.Component<AnchorTitleProps, AnchorTitleState> {
+ constructor(props: AnchorTitleProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ let opacity = 0;
+ if (this.props.shouldShowAnchor) {
+ if (this.state.isHovering) {
+ opacity = 0.6;
+ } else {
+ opacity = 1;
+ }
+ }
+ return (
+ <div className="relative flex" style={{...styles[this.props.headerSize], ...styles.headers}}>
+ <div
+ className="inline-block"
+ style={{paddingRight: 4}}
+ >
+ {this.props.title}
+ </div>
+ <ScrollLink
+ to={this.props.id}
+ offset={headerSizeToScrollOffset[this.props.headerSize]}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <i
+ className="zmdi zmdi-link"
+ onClick={utils.setUrlHash.bind(utils, this.props.id)}
+ style={{...styles.anchor, opacity}}
+ onMouseOver={this.setHoverState.bind(this, true)}
+ onMouseOut={this.setHoverState.bind(this, false)}
+ />
+ </ScrollLink>
+ </div>
+ );
+ }
+ private setHoverState(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/website/ts/pages/shared/markdown_code_block.tsx
new file mode 100644
index 000000000..621e5b606
--- /dev/null
+++ b/packages/website/ts/pages/shared/markdown_code_block.tsx
@@ -0,0 +1,20 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as HighLight from 'react-highlight';
+
+interface MarkdownCodeBlockProps {
+ literal: string;
+ language: string;
+}
+
+export function MarkdownCodeBlock(props: MarkdownCodeBlockProps) {
+ return (
+ <span style={{fontSize: 16}}>
+ <HighLight
+ className={props.language || 'js'}
+ >
+ {props.literal}
+ </HighLight>
+ </span>
+ );
+}
diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx
new file mode 100644
index 000000000..32b55abc8
--- /dev/null
+++ b/packages/website/ts/pages/shared/markdown_section.tsx
@@ -0,0 +1,77 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import {Element as ScrollElement} from 'react-scroll';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {utils} from 'ts/utils/utils';
+import {MarkdownCodeBlock} from 'ts/pages/shared/markdown_code_block';
+import RaisedButton from 'material-ui/RaisedButton';
+import {HeaderSizes} from 'ts/types';
+
+interface MarkdownSectionProps {
+ sectionName: string;
+ markdownContent: string;
+ headerSize?: HeaderSizes;
+ githubLink?: string;
+}
+
+interface MarkdownSectionState {
+ shouldShowAnchor: boolean;
+}
+
+export class MarkdownSection extends React.Component<MarkdownSectionProps, MarkdownSectionState> {
+ public static defaultProps: Partial<MarkdownSectionProps> = {
+ headerSize: HeaderSizes.H3,
+ };
+ constructor(props: MarkdownSectionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const sectionName = this.props.sectionName;
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ className="pt2 pr3 md-pl2 sm-pl3 overflow-hidden"
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <ScrollElement name={id}>
+ <div className="clearfix">
+ <div className="col lg-col-8 md-col-8 sm-col-12">
+ <span style={{textTransform: 'capitalize'}}>
+ <AnchorTitle
+ headerSize={this.props.headerSize}
+ title={sectionName}
+ id={id}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </span>
+ </div>
+ <div className="col col-4 sm-hide xs-hide py2 right-align">
+ {!_.isUndefined(this.props.githubLink) &&
+ <RaisedButton
+ href={this.props.githubLink}
+ target="_blank"
+ label="Edit on Github"
+ icon={<i className="zmdi zmdi-github" style={{fontSize: 23}} />}
+ />
+ }
+ </div>
+ </div>
+ <ReactMarkdown
+ source={this.props.markdownContent}
+ renderers={{CodeBlock: MarkdownCodeBlock}}
+ />
+ </ScrollElement>
+ </div>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx
new file mode 100644
index 000000000..e69506bb8
--- /dev/null
+++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx
@@ -0,0 +1,163 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import MenuItem from 'material-ui/MenuItem';
+import {colors} from 'material-ui/styles';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {VersionDropDown} from 'ts/pages/shared/version_drop_down';
+import {ZeroExJsDocSections, Styles, MenuSubsectionsBySection, Docs} from 'ts/types';
+import {typeDocUtils} from 'ts/utils/typedoc_utils';
+import {Link as ScrollLink} from 'react-scroll';
+
+interface NestedSidebarMenuProps {
+ topLevelMenu: {[topLevel: string]: string[]};
+ menuSubsectionsBySection: MenuSubsectionsBySection;
+ shouldDisplaySectionHeaders?: boolean;
+ onMenuItemClick?: () => void;
+ selectedVersion?: string;
+ versions?: string[];
+ doc?: Docs;
+ isSectionHeaderClickable?: boolean;
+}
+
+interface NestedSidebarMenuState {}
+
+const styles: Styles = {
+ menuItemWithHeaders: {
+ minHeight: 0,
+ },
+ menuItemWithoutHeaders: {
+ minHeight: 48,
+ },
+ menuItemInnerDivWithHeaders: {
+ lineHeight: 2,
+ },
+};
+
+export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, NestedSidebarMenuState> {
+ public static defaultProps: Partial<NestedSidebarMenuProps> = {
+ shouldDisplaySectionHeaders: true,
+ onMenuItemClick: _.noop,
+ };
+ public render() {
+ const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => {
+ const finalSectionName = sectionName.replace(/-/g, ' ');
+ if (this.props.shouldDisplaySectionHeaders) {
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py1"
+ >
+ <ScrollLink
+ to={id}
+ offset={-20}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <div
+ style={{color: colors.grey500, cursor: 'pointer'}}
+ className="pb1"
+ >
+ {finalSectionName.toUpperCase()}
+ </div>
+ </ScrollLink>
+ {this.renderMenuItems(menuItems)}
+ </div>
+ );
+ } else {
+ return (
+ <div key={`section-${sectionName}`} >
+ {this.renderMenuItems(menuItems)}
+ </div>
+ );
+ }
+ });
+ return (
+ <div>
+ {!_.isUndefined(this.props.versions) &&
+ !_.isUndefined(this.props.selectedVersion) &&
+ !_.isUndefined(this.props.doc) &&
+ <VersionDropDown
+ selectedVersion={this.props.selectedVersion}
+ versions={this.props.versions}
+ doc={this.props.doc}
+ />
+ }
+ {navigation}
+ </div>
+ );
+ }
+ private renderMenuItems(menuItemNames: string[]): React.ReactNode[] {
+ const menuItemStyles = this.props.shouldDisplaySectionHeaders ?
+ styles.menuItemWithHeaders :
+ styles.menuItemWithoutHeaders;
+ const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ?
+ styles.menuItemInnerDivWithHeaders : {};
+ const menuItems = _.map(menuItemNames, menuItemName => {
+ const id = utils.getIdFromName(menuItemName);
+ return (
+ <div key={menuItemName}>
+ <ScrollLink
+ key={`menuItem-${menuItemName}`}
+ to={id}
+ offset={-10}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <MenuItem
+ onTouchTap={this.onMenuItemClick.bind(this, menuItemName)}
+ style={menuItemStyles}
+ innerDivStyle={menuItemInnerDivStyles}
+ >
+ <span style={{textTransform: 'capitalize'}}>
+ {menuItemName}
+ </span>
+ </MenuItem>
+ </ScrollLink>
+ {this.renderMenuItemSubsections(menuItemName)}
+ </div>
+ );
+ });
+ return menuItems;
+ }
+ private renderMenuItemSubsections(menuItemName: string): React.ReactNode {
+ if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) {
+ return null;
+ }
+ return this.renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]);
+ }
+ private renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode {
+ return (
+ <ul style={{margin: 0, listStyleType: 'none', paddingLeft: 0}} key={menuItemName}>
+ {_.map(entityNames, entityName => {
+ const id = utils.getIdFromName(entityName);
+ return (
+ <li key={`menuItem-${entityName}`}>
+ <ScrollLink
+ to={id}
+ offset={0}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ onTouchTap={this.onMenuItemClick.bind(this, entityName)}
+ >
+ <MenuItem
+ onTouchTap={this.onMenuItemClick.bind(this, menuItemName)}
+ style={{minHeight: 35}}
+ innerDivStyle={{paddingLeft: 36, fontSize: 14, lineHeight: '35px'}}
+ >
+ {entityName}
+ </MenuItem>
+ </ScrollLink>
+ </li>
+ );
+ })}
+ </ul>
+ );
+ }
+ private onMenuItemClick(menuItemName: string): void {
+ const id = utils.getIdFromName(menuItemName);
+ utils.setUrlHash(id);
+ this.props.onMenuItemClick();
+ }
+}
diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx
new file mode 100644
index 000000000..5937be13b
--- /dev/null
+++ b/packages/website/ts/pages/shared/section_header.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import {Element as ScrollElement} from 'react-scroll';
+import {AnchorTitle} from 'ts/pages/shared/anchor_title';
+import {utils} from 'ts/utils/utils';
+import {HeaderSizes} from 'ts/types';
+
+interface SectionHeaderProps {
+ sectionName: string;
+ headerSize?: HeaderSizes;
+}
+
+interface SectionHeaderState {
+ shouldShowAnchor: boolean;
+}
+
+export class SectionHeader extends React.Component<SectionHeaderProps, SectionHeaderState> {
+ public static defaultProps: Partial<SectionHeaderProps> = {
+ headerSize: HeaderSizes.H2,
+ };
+ constructor(props: SectionHeaderProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const sectionName = this.props.sectionName.replace(/-/g, ' ');
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ onMouseOver={this.setAnchorVisibility.bind(this, true)}
+ onMouseOut={this.setAnchorVisibility.bind(this, false)}
+ >
+ <ScrollElement name={id}>
+ <AnchorTitle
+ headerSize={this.props.headerSize}
+ title={<span style={{textTransform: 'capitalize'}}>{sectionName}</span>}
+ id={id}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </ScrollElement>
+ </div>
+ );
+ }
+ private setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx
new file mode 100644
index 000000000..f29547c9c
--- /dev/null
+++ b/packages/website/ts/pages/shared/version_drop_down.tsx
@@ -0,0 +1,46 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import MenuItem from 'material-ui/MenuItem';
+import DropDownMenu from 'material-ui/DropDownMenu';
+import {constants} from 'ts/utils/constants';
+import {Docs} from 'ts/types';
+
+interface VersionDropDownProps {
+ selectedVersion: string;
+ versions: string[];
+ doc: Docs;
+}
+
+interface VersionDropDownState {}
+
+export class VersionDropDown extends React.Component<VersionDropDownProps, VersionDropDownState> {
+ public render() {
+ return (
+ <div className="mx-auto" style={{width: 120}}>
+ <DropDownMenu
+ maxHeight={300}
+ value={this.props.selectedVersion}
+ onChange={this.updateSelectedVersion.bind(this)}
+ >
+ {this.renderDropDownItems()}
+ </DropDownMenu>
+ </div>
+ );
+ }
+ private renderDropDownItems() {
+ const items = _.map(this.props.versions, version => {
+ return (
+ <MenuItem
+ key={version}
+ value={version}
+ primaryText={`v${version}`}
+ />
+ );
+ });
+ return items;
+ }
+ private updateSelectedVersion(e: any, index: number, value: string) {
+ const docPath = constants.docToPath[this.props.doc];
+ window.location.href = `${docPath}/${value}${window.location.hash}`;
+ }
+}
diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx
new file mode 100644
index 000000000..0e6fc98ab
--- /dev/null
+++ b/packages/website/ts/pages/wiki/wiki.tsx
@@ -0,0 +1,210 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import DocumentTitle = require('react-document-title');
+import {colors} from 'material-ui/styles';
+import CircularProgress from 'material-ui/CircularProgress';
+import {
+ scroller,
+} from 'react-scroll';
+import {Styles, Article, ArticlesBySection} from 'ts/types';
+import {TopBar} from 'ts/components/top_bar';
+import {HeaderSizes, WebsitePaths} from 'ts/types';
+import {utils} from 'ts/utils/utils';
+import {constants} from 'ts/utils/constants';
+import {configs} from 'ts/utils/configs';
+import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
+import {SectionHeader} from 'ts/pages/shared/section_header';
+import {MarkdownSection} from 'ts/pages/shared/markdown_section';
+
+const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000;
+
+export interface WikiProps {
+ source: string;
+ location: Location;
+}
+
+interface WikiState {
+ articlesBySection: ArticlesBySection;
+}
+
+const styles: Styles = {
+ mainContainers: {
+ position: 'absolute',
+ top: 60,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ overflowZ: 'hidden',
+ overflowY: 'scroll',
+ minHeight: 'calc(100vh - 60px)',
+ WebkitOverflowScrolling: 'touch',
+ },
+ menuContainer: {
+ borderColor: colors.grey300,
+ maxWidth: 330,
+ marginLeft: 20,
+ },
+};
+
+export class Wiki extends React.Component<WikiProps, WikiState> {
+ private wikiBackoffTimeoutId: number;
+ constructor(props: WikiProps) {
+ super(props);
+ this.state = {
+ articlesBySection: undefined,
+ };
+ }
+ public componentWillMount() {
+ this.fetchArticlesBySectionAsync();
+ }
+ public componentWillUnmount() {
+ clearTimeout(this.wikiBackoffTimeoutId);
+ }
+ public render() {
+ const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection)
+ ? {}
+ : this.getMenuSubsectionsBySection(this.state.articlesBySection);
+ return (
+ <div>
+ <DocumentTitle title="0x Protocol Wiki"/>
+ <TopBar
+ blockchainIsLoaded={false}
+ location={this.props.location}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ shouldFullWidth={true}
+ />
+ {_.isUndefined(this.state.articlesBySection) ?
+ <div
+ className="col col-12"
+ style={styles.mainContainers}
+ >
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{paddingBottom: 11}}>Loading wiki...</div>
+ </div>
+ </div> :
+ <div
+ className="mx-auto flex"
+ style={{color: colors.grey800, height: 43}}
+ >
+ <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
+ <div
+ className="border-right absolute pt2"
+ style={{...styles.menuContainer, ...styles.mainContainers}}
+ >
+ <NestedSidebarMenu
+ topLevelMenu={menuSubsectionsBySection}
+ menuSubsectionsBySection={menuSubsectionsBySection}
+ isSectionHeaderClickable={true}
+ />
+ </div>
+ </div>
+ <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
+ <div
+ id="documentation"
+ style={styles.mainContainers}
+ className="absolute"
+ >
+ <div id="0xProtocolWiki" />
+ <h1 className="md-pl2 sm-pl3">
+ <a href={constants.GITHUB_WIKI_URL} target="_blank">
+ 0x Protocol Wiki
+ </a>
+ </h1>
+ <div id="wiki">
+ {this.renderWikiArticles()}
+ </div>
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ );
+ }
+ private renderWikiArticles(): React.ReactNode {
+ const sectionNames = _.keys(this.state.articlesBySection);
+ const sections = _.map(sectionNames, sectionName => this.renderSection(sectionName));
+ return sections;
+ }
+ private renderSection(sectionName: string) {
+ const articles = this.state.articlesBySection[sectionName];
+ const renderedArticles = _.map(articles, (article: Article) => {
+ const githubLink = `${constants.GITHUB_WIKI_URL}/edit/master/${sectionName}/${article.fileName}`;
+ return (
+ <div key={`markdown-section-${article.title}`}>
+ <MarkdownSection
+ sectionName={article.title}
+ markdownContent={article.content}
+ headerSize={HeaderSizes.H2}
+ githubLink={githubLink}
+ />
+ <div className="mb4 mt3 p3 center" style={{backgroundColor: '#f9f5ef'}}>
+ See a way to make this article better?{' '}
+ <a
+ href={githubLink}
+ target="_blank"
+ >
+ Edit here →
+ </a>
+ </div>
+ </div>
+ );
+ });
+ return (
+ <div
+ key={`section-${sectionName}`}
+ className="py2 pr3 md-pl2 sm-pl3"
+ >
+ <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} />
+ {renderedArticles}
+ </div>
+ );
+ }
+ private scrollToHash(): void {
+ const hashWithPrefix = this.props.location.hash;
+ let hash = hashWithPrefix.slice(1);
+ if (_.isEmpty(hash)) {
+ hash = '0xProtocolWiki'; // scroll to the top
+ }
+
+ scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
+ }
+ private async fetchArticlesBySectionAsync(): Promise<void> {
+ const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`;
+ const response = await fetch(endpoint);
+ if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) {
+ // We need to backoff and try fetching again later
+ this.wikiBackoffTimeoutId = window.setTimeout(() => {
+ this.fetchArticlesBySectionAsync();
+ }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS);
+ return;
+ }
+ if (response.status !== 200) {
+ // TODO: Show the user an error message when the wiki fail to load
+ const errMsg = await response.text();
+ utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`);
+ return;
+ }
+ const articlesBySection = await response.json();
+ this.setState({
+ articlesBySection,
+ }, () => {
+ this.scrollToHash();
+ });
+ }
+ private getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) {
+ const sectionNames = _.keys(articlesBySection);
+ const menuSubsectionsBySection: {[section: string]: string[]} = {};
+ for (const sectionName of sectionNames) {
+ const articles = articlesBySection[sectionName];
+ const articleNames = _.map(articles, article => article.title);
+ menuSubsectionsBySection[sectionName] = articleNames;
+ }
+ return menuSubsectionsBySection;
+ }
+}