on master help sign in
- Added Passport component

- Move generate/download wallet functions to lib/invite - View and flow complete for INVITE_LOGIN and INVITE_WALLET stages

commitdfeb34768b9ec906f2809d5f01007fe0389d9337 authorChris Johnson <chris@cjohnson.io> dateFri 19 Apr 2019, 15:25 (8 years ago) parent26724ff
9 files changed+565−231
package-lock.json+13−32 src/bridge/components/Base.js+2 src/bridge/components/Header.js+20−11 src/bridge/components/Passport.js+142 src/bridge/lib/invite.js+108 src/bridge/style/extend.css+53 src/bridge/views/InviteLogin.js+196−57 src/bridge/views/InviteWallet.js+30−130 src/common/components/AppNavigation.js+1−1
package-lock.json+13−32
@@ -1251,7 +1251,7 @@
12511251 "flat": "^4.1.0",
12521252 "lodash": "^4.17.10",
12531253 "qr-image": "^3.2.0",
1254− "sigil-js": "github:urbit/sigil-js",
1254+ "sigil-js": "github:urbit/sigil-js#1864796bb3f482e597fd85acfd324795e42b2465",
12551255 "urbit-ob": "^1.4.4"
12561256 },
12571257 "dependencies": {
@@ -6419,8 +6419,7 @@
64196419 },
64206420 "ansi-regex": {
64216421 "version": "2.1.1",
6422− "bundled": true,
6423− "optional": true
6422+ "bundled": true
64246423 },
64256424 "aproba": {
64266425 "version": "1.2.0",
@@ -6438,13 +6437,11 @@
64386437 },
64396438 "balanced-match": {
64406439 "version": "1.0.0",
6441− "bundled": true,
6442− "optional": true
6440+ "bundled": true
64436441 },
64446442 "brace-expansion": {
64456443 "version": "1.1.11",
64466444 "bundled": true,
6447− "optional": true,
64486445 "requires": {
64496446 "balanced-match": "^1.0.0",
64506447 "concat-map": "0.0.1"
@@ -6457,18 +6454,15 @@
64576454 },
64586455 "code-point-at": {
64596456 "version": "1.1.0",
6460− "bundled": true,
6461− "optional": true
6457+ "bundled": true
64626458 },
64636459 "concat-map": {
64646460 "version": "0.0.1",
6465− "bundled": true,
6466− "optional": true
6461+ "bundled": true
64676462 },
64686463 "console-control-strings": {
64696464 "version": "1.1.0",
6470− "bundled": true,
6471− "optional": true
6465+ "bundled": true
64726466 },
64736467 "core-util-is": {
64746468 "version": "1.0.2",
@@ -6571,8 +6565,7 @@
65716565 },
65726566 "inherits": {
65736567 "version": "2.0.3",
6574− "bundled": true,
6575− "optional": true
6568+ "bundled": true
65766569 },
65776570 "ini": {
65786571 "version": "1.3.5",
@@ -6582,7 +6575,6 @@
65826575 "is-fullwidth-code-point": {
65836576 "version": "1.0.0",
65846577 "bundled": true,
6585− "optional": true,
65866578 "requires": {
65876579 "number-is-nan": "^1.0.0"
65886580 }
@@ -6595,20 +6587,17 @@
65956587 "minimatch": {
65966588 "version": "3.0.4",
65976589 "bundled": true,
6598− "optional": true,
65996590 "requires": {
66006591 "brace-expansion": "^1.1.7"
66016592 }
66026593 },
66036594 "minimist": {
66046595 "version": "0.0.8",
6605− "bundled": true,
6606− "optional": true
6596+ "bundled": true
66076597 },
66086598 "minipass": {
66096599 "version": "2.2.4",
66106600 "bundled": true,
6611− "optional": true,
66126601 "requires": {
66136602 "safe-buffer": "^5.1.1",
66146603 "yallist": "^3.0.0"
@@ -6625,7 +6614,6 @@
66256614 "mkdirp": {
66266615 "version": "0.5.1",
66276616 "bundled": true,
6628− "optional": true,
66296617 "requires": {
66306618 "minimist": "0.0.8"
66316619 }
@@ -6698,8 +6686,7 @@
66986686 },
66996687 "number-is-nan": {
67006688 "version": "1.0.1",
6701− "bundled": true,
6702− "optional": true
6689+ "bundled": true
67036690 },
67046691 "object-assign": {
67056692 "version": "4.1.1",
@@ -6709,7 +6696,6 @@
67096696 "once": {
67106697 "version": "1.4.0",
67116698 "bundled": true,
6712− "optional": true,
67136699 "requires": {
67146700 "wrappy": "1"
67156701 }
@@ -6785,8 +6771,7 @@
67856771 },
67866772 "safe-buffer": {
67876773 "version": "5.1.1",
6788− "bundled": true,
6789− "optional": true
6774+ "bundled": true
67906775 },
67916776 "safer-buffer": {
67926777 "version": "2.1.2",
@@ -6816,7 +6801,6 @@
68166801 "string-width": {
68176802 "version": "1.0.2",
68186803 "bundled": true,
6819− "optional": true,
68206804 "requires": {
68216805 "code-point-at": "^1.0.0",
68226806 "is-fullwidth-code-point": "^1.0.0",
@@ -6834,7 +6818,6 @@
68346818 "strip-ansi": {
68356819 "version": "3.0.1",
68366820 "bundled": true,
6837− "optional": true,
68386821 "requires": {
68396822 "ansi-regex": "^2.0.0"
68406823 }
@@ -6873,13 +6856,11 @@
68736856 },
68746857 "wrappy": {
68756858 "version": "1.0.2",
6876− "bundled": true,
6877− "optional": true
6859+ "bundled": true
68786860 },
68796861 "yallist": {
68806862 "version": "3.0.2",
6881− "bundled": true,
6882− "optional": true
6863+ "bundled": true
68836864 }
68846865 }
68856866 },
@@ -19611,7 +19592,7 @@
1961119592 "requires": {
1961219593 "underscore": "1.8.3",
1961319594 "web3-core-helpers": "1.0.0-beta.33",
19614− "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible"
19595+ "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2"
1961519596 },
1961619597 "dependencies": {
1961719598 "websocket": {
src/bridge/components/Base.js+2
@@ -63,6 +63,7 @@ import ValidatedSigil from './ValidatedSigil';
6363import ValidatedBlockie from './ValidatedBlockie';
6464import Chevron from './icons/Chevron'
6565import ShowBlockie from './ShowBlockie'
66+import Passport from './Passport'
6667
6768// HOC validated form components
6869
@@ -202,4 +203,5 @@ export {
202203 Warning,
203204
204205 Chevron,
206+ Passport
205207};
src/bridge/components/Header.js+20−11
@@ -1,7 +1,7 @@
11import React from 'react'
22import { Button, Chevron } from '../components/Base'
33
4−import { renderRoute } from '../lib/router'
4+import { renderRoute, ROUTE_NAMES } from '../lib/router'
55import { isLast } from '../lib/lib'
66
77const Crumbs = (props) => {
@@ -40,15 +40,24 @@ const Crumbs = (props) => {
4040 return rendered
4141}
4242
43−const Header = (props) =>
44− <div className={'flex items-center h-10'}>
45− <Crumbs
46− routeCrumbs={ props.routeCrumbs }
47− skipRoute={ props.skipRoute }
48− networkType={ props.networkType }
49− wallet={ props.wallet }
50− pointCursor={ props.pointCursor }
51− />
52− </div>
43+const Header = (props) => {
44+ let showCrumbs = !props.routeCrumbs.contains(ROUTE_NAMES.INVITE_LOGIN)
45+
46+ if (showCrumbs) {
47+ return (
48+ <div className={'flex items-center h-10'}>
49+ <Crumbs
50+ routeCrumbs={ props.routeCrumbs }
51+ skipRoute={ props.skipRoute }
52+ networkType={ props.networkType }
53+ wallet={ props.wallet }
54+ pointCursor={ props.pointCursor }
55+ />
56+ </div>
57+ )
58+ } else {
59+ return <h2 className="mt-9">Bridge</h2>
60+ }
61+}
5362
5463export default Header
src/bridge/components/Passport.jsnew file+142
@@ -0,0 +1,142 @@
1+import React from 'react'
2+import { pour } from 'sigil-js'
3+import * as ob from 'urbit-ob'
4+import ReactSVGComponents from './ReactSVGComponents'
5+import PropTypes from 'prop-types'
6+import PaperCollateralRenderer from 'PaperCollateralRenderer'
7+import { Button } from './Base'
8+
9+import { downloadWallet } from '../lib/invite'
10+
11+class Passport extends React.Component {
12+
13+ constructor(props) {
14+ super(props)
15+
16+ this.state = {
17+ paper: null
18+ }
19+
20+ this.masterScreen = this.generateScreen()
21+ this.managementScreen = this.generateScreen()
22+
23+ this.download = this.download.bind(this)
24+ }
25+
26+ download() {
27+ downloadWallet(this.state.paper)
28+ this.props.confirmWalletDownload()
29+ }
30+
31+ generateScreen() {
32+ let blocks = [];
33+
34+ for (var i = 0; i < 80; i++) {
35+ let rand = Math.round(Math.random())
36+ let className = rand ? "passport-block bg-gray-30" : "passport-block"
37+
38+ blocks.push(<div key={i} className={className}></div>)
39+ }
40+
41+ return (
42+ <div className="flex flex-wrap">
43+ {blocks}
44+ </div>
45+ )
46+ }
47+
48+ render() {
49+ let sigil = null
50+
51+ console.log('this.props.point = ', this.props.point)
52+
53+ let patp = this.props.point.matchWith({
54+ Just: p => ob.patp(p.value),
55+ Nothing: () => ''
56+ })
57+
58+ let wallet = this.props.wallet.matchWith({
59+ Just: w => w.value,
60+ Nothing: () => null
61+ })
62+
63+ if (patp !== '') {
64+ sigil = pour({
65+ patp: patp,
66+ renderer: ReactSVGComponents,
67+ size: 128
68+ })
69+ } else {
70+ sigil = <div className="passport-sigil-blank"></div>
71+ }
72+
73+ return (
74+ <div className="passport">
75+ <div className="flex">
76+ <div className="passport-sigil">
77+ {sigil}
78+ </div>
79+ <div className="passport-metadata ml-4">
80+ <div className="passport-label text-mono">Point</div>
81+ <div className="text-mono">{patp || '-'}</div>
82+
83+ <div className="passport-label gray-50 mt-6">Created on</div>
84+ <div className="passport-value">{this.props.createdOn || '-'}</div>
85+
86+ <div className="passport-label gray-50 mt-2">Invited by</div>
87+ <div className="passport-value">{this.props.invitedBy || '-'}</div>
88+ </div>
89+ </div>
90+
91+ <div className="mt-8">
92+ <div className="passport-label text-mono">Ethereum Address</div>
93+ <div className="passport-value">{this.props.ethAddress || 'Ungenerated'}</div>
94+
95+ <div className="passport-label gray-50 mt-3">Master Ticket</div>
96+ <div className="passport-value">{this.props.ethAddress ? this.masterScreen : 'Ungenerated'}</div>
97+
98+ <div className="passport-label gray-50 mt-5">Managment Seed</div>
99+ <div className="passport-value">{this.props.ethAddress ? this.managmentScreen : 'Ungenerated'}</div>
100+
101+ <Button
102+ disabled={ !this.state.paper }
103+ className={ 'mt-8' }
104+ prop-size={ 'lg wide' }
105+ prop-color={ 'green' }
106+ onClick={ this.download }
107+ >
108+ { 'Download' }
109+ </Button>
110+ </div>
111+
112+ {wallet &&
113+ <PaperCollateralRenderer
114+ wallet={this.props.wallet}
115+ className={'extremely-hidden'}
116+ callback={data => {
117+ this.setState({paper: data})
118+ }}
119+ mode={'REGISTRATION'} />
120+ }
121+ </div>
122+ )
123+ }
124+}
125+
126+Passport.propTypes = {
127+ 'patp': PropTypes.string,
128+ 'createdOn': PropTypes.string,
129+ 'invitedBy': PropTypes.string,
130+ 'ethAddress': PropTypes.string,
131+ wallet: PropTypes.object
132+};
133+
134+Passport.defaultProps = {
135+ 'patp': '',
136+ 'createdOn': '',
137+ 'invitedBy': '',
138+ 'ethAddress': '',
139+ wallet: null
140+};
141+
142+export default Passport
src/bridge/lib/invite.jsnew file+108
@@ -0,0 +1,108 @@
1+import lodash from 'lodash'
2+import * as more from 'more-entropy'
3+import * as ob from 'urbit-ob'
4+import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
5+import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
6+ GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
7+ SEED_ENTROPY_BITS,
8+ GEN_STATES
9+ } from '../../walletgen/lib/constants'
10+
11+import JSZip from 'jszip'
12+import saveAs from 'file-saver'
13+
14+const NEXT_STEP_NUM = 6;
15+const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
16+
17+async function generateWallet(point) {
18+ const makeTicket = point => {
19+
20+ const bits = point < MIN_STAR
21+ ? GALAXY_ENTROPY_BITS
22+ : point < MIN_PLANET
23+ ? STAR_ENTROPY_BITS
24+ : PLANET_ENTROPY_BITS
25+
26+ const bytes = bits / 8
27+ const some = new Uint8Array(bytes)
28+ window.crypto.getRandomValues(some)
29+
30+ const gen = new more.Generator()
31+
32+ return new Promise((resolve, reject) => {
33+ gen.generate(bits, result => {
34+ const chunked = lodash.chunk(result, 2)
35+ const desired = chunked.slice(0, bytes) // only take required entropy
36+ const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1])
37+ const entropy = lodash.zipWith(some, more, (x, y) => x ^ y)
38+ const buf = Buffer.from(entropy)
39+ const patq = ob.hex2patq(buf.toString('hex'))
40+ resolve(patq)
41+ reject('Entropy generation failed')
42+ })
43+ })
44+ }
45+
46+ const genWallet = async (point, ticket, cb) => {
47+
48+ const config = {
49+ ticket: ticket,
50+ seedSize: SEED_LENGTH_BYTES,
51+ ship: point,
52+ password: '',
53+ revisions: {},
54+ boot: false //TODO should this generate networking keys here already?
55+ };
56+
57+ const wallet = await kg.generateWallet(config);
58+
59+ // This is here to notify the anyone who opens console because the thread
60+ // hangs, blocking UI updates so this cannot be doen in the UI
61+ console.log('Generating Wallet for point address: ', point);
62+
63+ return wallet;
64+ }
65+
66+ const ticket = await makeTicket(point);
67+
68+ const wallet = await genWallet(point, ticket);
69+ console.log('got wallet', wallet);
70+ console.log('should start rendering now');
71+ return wallet;
72+}
73+
74+//TODO pulled from walletgen/views/Generate and Download, put into lib
75+async function downloadWallet(paper) {
76+ return new Promise((resolve, reject) => {
77+ const zip = new JSZip();
78+ //TODO the categories here aren't explained in bridge at all...
79+
80+ const bin0 = paper.filter(item => item.bin === '0');
81+ const bin1 = paper.filter(item => item.bin === '1');
82+ const bin2 = paper.filter(item => item.bin === '2');
83+ const bin3 = paper.filter(item => item.bin === '3');
84+ const bin4 = paper.filter(item => item.bin === '4');
85+
86+ const bin0Folder = zip.folder("0. Public");
87+ const bin1Folder = zip.folder("1. Very High Friction Custody");
88+ const bin2Folder = zip.folder("2. High Friction Custody");
89+ const bin3Folder = zip.folder("3. Medium Friction Custody");
90+ const bin4Folder = zip.folder("4. Low Friction Custody");
91+
92+ bin0.forEach(item => bin0Folder.file(`${item.pageTitle}.png`, item.png))
93+ bin1.forEach(item => bin1Folder.file(`${item.pageTitle}.png`, item.png));
94+ bin2.forEach(item => bin2Folder.file(`${item.pageTitle}.png`, item.png));
95+ bin3.forEach(item => bin3Folder.file(`${item.pageTitle}.png`, item.png));
96+ bin4.forEach(item => bin4Folder.file(`${item.pageTitle}.png`, item.png));
97+
98+ zip.generateAsync({type:"blob"}).then((content) => {
99+ saveAs(content, 'urbit-wallet.zip');
100+ resolve(true)
101+ });
102+ })
103+}
104+
105+export {
106+ generateWallet,
107+ downloadWallet
108+}
src/bridge/style/extend.css+53
@@ -212,6 +212,10 @@ footer,
212212 font-size: 8rem;
213213}
214214
215+.lh-8 {
216+ line-height: 8rem;
217+}
218+
215219.ls-1 > * {
216220 padding-bottom: 1rem !important;
217221}
@@ -481,3 +485,52 @@ code {
481485.block {
482486 display: block;
483487}
488+
489+.flex-wrap {
490+ flex-wrap: wrap;
491+}
492+
493+/**
494+ Passport styles
495+**/
496+
497+.passport {
498+ border: 1px solid #C5FBE7;
499+ padding: 24px;
500+}
501+
502+.passport-block {
503+ width: 8px;
504+ height: 8px;
505+}
506+
507+.passport-label {
508+ font-size: 12px;
509+ line-height: 24px;
510+ font-weight: 700;
511+ font-family: "Inter UI";
512+ color: #7f7f7f;
513+}
514+
515+.passport-value {
516+ font-size: 12px;
517+ line-height: 24px;
518+ font-family: "Source Code Pro";
519+ color: #7f7f7f;
520+}
521+
522+.passport-metadata .passport-label {
523+ line-height: 12px;
524+}
525+
526+.passport-point {
527+ font-size: 16px;
528+ line-height: 24px;
529+}
530+
531+.passport-sigil-blank {
532+ width: 128px;
533+ height: 128px;
534+
535+ background-color: black;
536+}
src/bridge/views/InviteLogin.js+196−57
@@ -1,13 +1,16 @@
11import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
3−import { Button } from '../components/Base'
4−import { InnerLabel, ValidatedSigil, PointInput, TicketInput } from '../components/Base'
5−import { Row, Col, H1, P } from '../components/Base'
3+import { InnerLabel, ValidatedSigil, PointInput,
4+ TicketInput, Button, Row, Col, H1, P, Passport } from '../components/Base'
65import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
76import * as ob from 'urbit-ob'
7+import * as azimuth from 'azimuth-js'
88
99import { ROUTE_NAMES } from '../lib/router'
10−import { DEFAULT_HD_PATH, urbitWalletFromTicket } from '../lib/wallet'
10+import { DEFAULT_HD_PATH, urbitWalletFromTicket,
11+ walletFromMnemonic, addressFromSecp256k1Public } from '../lib/wallet'
12+import { BRIDGE_ERROR } from '../lib/error'
13+import { generateWallet } from '../lib/invite'
1114
1215const placeholder = (len) => {
1316 let bytes = window.crypto.getRandomValues(new Uint8Array(len))
@@ -18,21 +21,34 @@ const placeholder = (len) => {
1821 return ob.hex2patq(hex)
1922}
2023
24+const INVITE_STAGES = {
25+ INVITE_LOGIN: "invite login",
26+ INVITE_WALLET: "invite wallet",
27+ INVITE_VERIFY: "invite verify",
28+ INVITE_TRANSACTIONS: "invite transactions"
29+}
30+
2131class InviteLogin extends React.Component {
2232
2333 constructor(props) {
2434 super(props)
2535
2636 this.state = {
27− ticket: '',
28− pointName: '',
29− isUnlocking: false
37+ inviteTicket: '',
38+ verifyTicket: '',
39+ realPoint: Nothing(),
40+ realWallet: Nothing(),
41+ walletReady: false,
42+ continueReady: true,
43+ stage: INVITE_STAGES.INVITE_LOGIN
3044 }
3145
3246 this.pointPlaceholder = placeholder(4)
3347 this.ticketPlaceholder = placeholder(12)
3448
35− this.handleTicketInput = this.handleTicketInput.bind(this)
49+ this.handleInviteTicketInput = this.handleInviteTicketInput.bind(this)
50+ this.handleVerifyTicketInput = this.handleVerifyTicketInput.bind(this)
51+ this.confirmWalletDownload = this.confirmWalletDownload.bind(this)
3652 this.navigateLogin = this.navigateLogin.bind(this)
3753 }
3854
@@ -41,72 +57,195 @@ class InviteLogin extends React.Component {
4157 this.props.pushRoute(ROUTE_NAMES.NETWORK)
4258 }
4359
44− handleTicketInput(ticket) {
45− this.setState({ ticket })
60+ handleInviteTicketInput(inviteTicket) {
61+ this.setState({ inviteTicket })
4662 }
4763
48− async walletFromTicket(ticket, pointName) {
64+ handleVerifyTicketInput(verifyTicket) {
65+ this.setState({ verifyTicket })
66+ }
67+
68+ confirmWalletDownload() {
4969 this.setState({
50− isUnlocking: true
70+ continueReady: true
71+ })
72+ }
73+
74+ async unlockInviteWallet(inviteTicket) {
75+ this.setState({
76+ continueReady: false,
77+ stage: INVITE_STAGES.INVITE_WALLET
78+ });
79+
80+ const uhdw = await urbitWalletFromTicket(inviteTicket, '~zod');
81+ const mnemonic = uhdw.ownership.seed;
82+ const inviteWallet = walletFromMnemonic(
83+ mnemonic,
84+ DEFAULT_HD_PATH,
85+ uhdw.meta.passphrase
86+ ).matchWith({
87+ Just: w => w.value,
88+ Nothing: () => null
5189 });
5290
53− const uhdw = await urbitWalletFromTicket(ticket, '~zod');
54− this.props.setUrbitWallet(Just(uhdw));
91+ const addr = addressFromSecp256k1Public(inviteWallet.publicKey);
92+
93+ const ctrcs = this.props.contracts.matchWith({
94+ Just: ctrcs => ctrcs.value,
95+ Nothing: () => {
96+ throw BRIDGE_ERROR.MISSING_CONTRACTS
97+ }
98+ })
99+
100+ const incoming = await azimuth.azimuth.getTransferringFor(ctrcs, addr)
101+
102+ let point
103+ let wallet
104+
105+ if (incoming.length > 0) {
106+ let pointNum = parseInt(incoming[0]);
107+ point = Just(pointNum);
108+ wallet = await generateWallet(pointNum);
109+ if (incoming.length > 1) {
110+ //TODO notify user "...and others / ticket reusable"
111+ }
112+ }
55113
56114 this.setState({
57− isUnlocking: false
58− });
59− this.props.pushRoute(ROUTE_NAMES.INVITE_WALLET);
115+ realPoint: point,
116+ realWallet: Just(wallet)
117+ })
118+ }
119+
120+ getTicketElement() {
121+ const phTick = this.ticketPlaceholder
122+
123+ let isLogin = this.state.stage === INVITE_STAGES.INVITE_LOGIN
124+ let isVerify = this.state.stage === INVITE_STAGES.INVITE_VERIFY
125+
126+ if (isLogin || isVerify) {
127+
128+ let value = isLogin ? this.state.inviteTicket : this.state.verifyTicket
129+ let handler = isLogin ? this.handleInviteTicketInput : this.handleVerifyTicketInput
130+ let label = isLogin ? 'Activation code' : 'ticket'
131+
132+ return (
133+ <TicketInput
134+ className='mono mt-8'
135+ prop-size='md'
136+ prop-format='innerLabel'
137+ type='text'
138+ autoFocus
139+ name='invite'
140+ placeholder={ `e.g. ${phTick}` }
141+ value={ value }
142+ onChange={ handler }>
143+ <InnerLabel>{ label }</InnerLabel>
144+ </TicketInput>
145+ )
146+ } else {
147+ return null
148+ }
149+ }
150+
151+ getStageText() {
152+ switch (this.state.stage) {
153+ case INVITE_STAGES.INVITE_LOGIN:
154+ return (
155+ <h1 className="text-700">Welcome</h1>
156+ )
157+ break
158+ case INVITE_STAGES.INVITE_WALLET:
159+ return (
160+ <div>
161+ <h1 className="fs-6 lh-8">Welcome</h1>
162+ <p>A passport is your digital identity. You will use your passport to access your true computer, send payments, and administer your identity. So naturally, you must keep this secure.</p>
163+ <p>After you’ve downloaded your passport, back up the ticket manually or store on a trusted device.</p>
164+ </div>
165+ )
166+ break
167+ }
168+ }
169+
170+ getContinueButton() {
171+ if (this.state.stage === INVITE_STAGES.INVITE_TRANSACTIONS) return null
172+
173+ let clickHandler
174+ let btnColor
175+
176+ switch(this.state.stage) {
177+ case INVITE_STAGES.INVITE_LOGIN:
178+ clickHandler = () => {
179+ this.unlockInviteWallet(this.state.inviteTicket)
180+ }
181+ btnColor = "black"
182+ break
183+ case INVITE_STAGES.INVITE_VERIFY:
184+ case INVITE_STAGES.INVITE_WALLET:
185+ btnColor = "green"
186+ break
187+ }
188+
189+ return (
190+ <Button
191+ className={'mt-4'}
192+ prop-size={'md'}
193+ prop-color={btnColor}
194+ disabled={!this.state.continueReady}
195+ onClick={clickHandler}
196+ >
197+ { 'Continue →' }
198+ </Button>
199+ )
200+ }
201+
202+ getStageDisplay() {
203+ const { stage } = this.state
204+
205+ return (
206+ <div className="mt-6 mb-8">
207+ <span className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_WALLET && 'gray-20'}`}>1 Passport</span>
208+ <span className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_VERIFY && 'gray-20'}`}>2 Verify</span>
209+ <span className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_TRANSACTIONS && 'gray-20'}`}>3 Invite</span>
210+ </div>
211+ )
60212 }
61213
62214 render() {
63215 const { wallet } = this.props
64− const { ticket, pointName } = this.state
216+ const { inviteTicket } = this.state
65217
66− const phTick = this.ticketPlaceholder
218+ let stageDisplay = this.getStageDisplay()
219+ let stageText = this.getStageText()
220+ let ticketElem = this.getTicketElement()
221+ let continueButton = this.getContinueButton()
67222
68223 return (
69224 <Row>
70− <Col className="col-md-4">
71− <H1>{ 'Welcome' }</H1>
72−
73− <TicketInput
74− className='mono mt-8'
75− prop-size='md'
76− prop-format='innerLabel'
77− type='text'
78− autoFocus
79− name='invite'
80− placeholder={ `e.g. ${phTick}` }
81− value={ ticket }
82− onChange={ this.handleTicketInput }>
83− <InnerLabel>{ 'Activation code' }</InnerLabel>
84− </TicketInput>
85−
86− <Button
87− className={'mt-8'}
88− prop-size={'lg'}
89− disabled={this.state.isUnlocking}
90− prop-color={'black'}
91− onClick={() => this.walletFromTicket(ticket, pointName)}>
92−
93− <span className="relative">
94− {this.state.isUnlocking &&
95− <span className="btn-spinner"></span>
96− }
97− {'Continue →'}
98− </span>
99− </Button>
100−
101− <Button
102− prop-type={`link`}
103− prop-size={`sm`}
104− className={`block mt-4`}
105− onClick={this.navigateLogin} >
106− {`Log in`}
107− </Button>
225+ <Col className="col-md-4 mt-">
226+ {stageDisplay}
227+ {stageText}
228+ {ticketElem}
229+ {continueButton}
230+
231+ { this.state.stage === INVITE_STAGES.INVITE_LOGIN &&
232+ <Button
233+ prop-type={`link`}
234+ prop-size={`sm`}
235+ className={`block mt-4`}
236+ onClick={this.navigateLogin} >
237+ {`Log in`}
238+ </Button>
239+ }
108240
109241 </Col>
242+ <Col className="col-md-offset-3 col-md-5 mt-18">
243+ <Passport
244+ point={this.state.realPoint}
245+ wallet={this.state.realWallet}
246+ confirmWalletDownload={this.confirmWalletDownload}
247+ />
248+ </Col>
110249 </Row>
111250 )
112251 }
src/bridge/views/InviteWallet.js+30−130
@@ -14,20 +14,10 @@ import Actions from './Point/Actions'
1414import { BRIDGE_ERROR } from '../lib/error'
1515import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
1616
17+import { generateWallet, downloadWallet } from '../lib/invite'
18+
1719// for wallet generation
18−import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
19−import * as more from 'more-entropy'
20−import lodash from 'lodash'
2120import PaperCollateralRenderer from 'PaperCollateralRenderer'
22−import JSZip from 'jszip'
23−import saveAs from 'file-saver'
24−import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
25− GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
26− SEED_ENTROPY_BITS,
27− GEN_STATES
28− } from '../../walletgen/lib/constants'
29−const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
30−const NEXT_STEP_NUM = 6;
3121
3222class InviteWallet extends React.Component {
3323
@@ -46,7 +36,7 @@ class InviteWallet extends React.Component {
4636 this.proceed = this.proceed.bind(this)
4737 }
4838
49− componentDidMount() {
39+ async componentDidMount() {
5040 const { web3, wallet, contracts, setPointCursor } = this.props
5141
5242 web3.chain(_ =>
@@ -58,15 +48,17 @@ class InviteWallet extends React.Component {
5848 console.log('incoming', incoming)
5949
6050 let point = Nothing();
51+ let wallet = Nothing();
52+
6153 if (incoming.length > 0) {
6254 let pointNum = parseInt(incoming[0]);
6355 point = Just(pointNum);
64− this.generateWallet(pointNum);
56+ wallet = await generateWallet(pointNum);
6557 if (incoming.length > 1) {
6658 //TODO notify user "...and others / ticket reusable"
6759 }
6860 }
69− this.setState({loaded: true, point: point})
61+ this.setState({loaded: true, point: point, wallet: Just(wallet)})
7062 })))
7163 }
7264
@@ -74,91 +66,11 @@ class InviteWallet extends React.Component {
7466 //
7567 }
7668
77− //TODO pulled from walletgen/views/Generate and Download, put into lib
78− async generateWallet(point) {
79− const makeTicket = point => {
80−
81− const bits = point < MIN_STAR
82− ? GALAXY_ENTROPY_BITS
83− : point < MIN_PLANET
84− ? STAR_ENTROPY_BITS
85− : PLANET_ENTROPY_BITS
86−
87− const bytes = bits / 8
88− const some = new Uint8Array(bytes)
89− window.crypto.getRandomValues(some)
90−
91− const gen = new more.Generator()
92−
93− return new Promise((resolve, reject) => {
94− gen.generate(bits, result => {
95− const chunked = lodash.chunk(result, 2)
96− const desired = chunked.slice(0, bytes) // only take required entropy
97− const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1])
98− const entropy = lodash.zipWith(some, more, (x, y) => x ^ y)
99− const buf = Buffer.from(entropy)
100− const patq = ob.hex2patq(buf.toString('hex'))
101− resolve(patq)
102− reject('Entropy generation failed')
103− })
104− })
105− }
106−
107− const genWallet = async (point, ticket, cb) => {
108−
109− const config = {
110− ticket: ticket,
111− seedSize: SEED_LENGTH_BYTES,
112− ship: point,
113− password: '',
114− revisions: {},
115− boot: false //TODO should this generate networking keys here already?
116− };
117−
118− const wallet = await kg.generateWallet(config);
119−
120− // This is here to notify the anyone who opens console because the thread
121− // hangs, blocking UI updates so this cannot be doen in the UI
122− console.log('Generating Wallet for point address: ', point);
123−
124− return wallet;
125− }
126−
127− const ticket = await makeTicket(point);
128−
129− const wallet = await genWallet(point, ticket);
130−
131− this.setState({ wallet: Just(wallet) });
132− console.log('got wallet', wallet);
133− console.log('should start rendering now');
134− }
135−
136− download() {
137− const zip = new JSZip();
138− //TODO the categories here aren't explained in bridge at all...
139−
140− const bin0 = this.state.paper.filter(item => item.bin === '0');
141− const bin1 = this.state.paper.filter(item => item.bin === '1');
142− const bin2 = this.state.paper.filter(item => item.bin === '2');
143− const bin3 = this.state.paper.filter(item => item.bin === '3');
144− const bin4 = this.state.paper.filter(item => item.bin === '4');
145−
146− const bin0Folder = zip.folder("0. Public");
147− const bin1Folder = zip.folder("1. Very High Friction Custody");
148− const bin2Folder = zip.folder("2. High Friction Custody");
149− const bin3Folder = zip.folder("3. Medium Friction Custody");
150− const bin4Folder = zip.folder("4. Low Friction Custody");
151−
152− bin0.forEach(item => bin0Folder.file(`${item.pageTitle}.png`, item.png))
153− bin1.forEach(item => bin1Folder.file(`${item.pageTitle}.png`, item.png));
154− bin2.forEach(item => bin2Folder.file(`${item.pageTitle}.png`, item.png));
155− bin3.forEach(item => bin3Folder.file(`${item.pageTitle}.png`, item.png));
156− bin4.forEach(item => bin4Folder.file(`${item.pageTitle}.png`, item.png));
157−
158− zip.generateAsync({type:"blob"}).then((content) => {
159− saveAs(content, 'urbit-wallet.zip');
160− this.setState({ downloaded: true });
161− });
69+ async download() {
70+ let result = await downloadWallet(this.state.paper)
71+ this.setState({
72+ downloaded: true
73+ })
16274 }
16375
16476 proceed() {
@@ -169,7 +81,6 @@ class InviteWallet extends React.Component {
16981 }
17082
17183 render() {
172−
17384 const { loaded, point, wallet, paper, downloaded } = this.state;
17485
17586 let loading;
@@ -189,24 +100,24 @@ class InviteWallet extends React.Component {
189100 );
190101 }
191102
192− let pointOverview;
193− if (loaded && Just.hasInstance(point)) {
194− const name = ob.patp(point.value);
195− const sigil = pour({
196− patp: name,
197− renderer: ReactSVGComponents,
198− size: 256
199− });
200− pointOverview = (
201− //TODO Passport display component
202− <>
203− <div className={'mt-12 pt-6'}>
204− { sigil }
205− </div>
206− <H3><code>{ name }</code></H3>
207− </>
208− );
209− }
103+ // let pointOverview;
104+ // if (loaded && Just.hasInstance(point)) {
105+ // const name = ob.patp(point.value);
106+ // const sigil = pour({
107+ // patp: name,
108+ // renderer: ReactSVGComponents,
109+ // size: 256
110+ // });
111+ // pointOverview = (
112+ // //TODO Passport display component
113+ // <>
114+ // <div className={'mt-12 pt-6'}>
115+ // { sigil }
116+ // </div>
117+ // <H3><code>{ name }</code></H3>
118+ // </>
119+ // );
120+ // }
210121
211122 let downloadButtonContents = 'Download wallet';
212123 if ( Nothing.hasInstance(this.state.wallet) ||
@@ -267,18 +178,7 @@ class InviteWallet extends React.Component {
267178 return (
268179 <Row>
269180 <Col>
270−
271− { loading }
272− { balanceWarning }
273− { pointOverview }
274−
275− <p>{'TODO copy. you need a wallet to receive the point into. download it below. also explanation of wallet files/storage'}</p>
276−
277− { downloadButton }
278− { nextButton }
279−
280181 { paperRenderer }
281−
282182 </Col>
283183 </Row>
284184 )
src/common/components/AppNavigation.js+1−1
@@ -24,7 +24,7 @@ class AppNavigation extends React.Component {
2424 position: "fixed",
2525 bottom: "10px",
2626 right: "10px",
27− "text-decoration": "underline",
27+ textDecoration: "underline",
2828 cursor: "pointer"
2929 }
3030