on master help sign in
- Added Passport component
dfeb347Chris Johnson
package-lock.json+13−32
@@ -1251,7 +1251,7 @@
1251 "flat": "^4.1.0",
1251 "flat": "^4.1.0",
1252 "lodash": "^4.17.10",
1252 "lodash": "^4.17.10",
1253 "qr-image": "^3.2.0",
1253 "qr-image": "^3.2.0",
1254− "sigil-js": "github:urbit/sigil-js",
1254+ "sigil-js": "github:urbit/sigil-js#1864796bb3f482e597fd85acfd324795e42b2465",
1255 "urbit-ob": "^1.4.4"
1255 "urbit-ob": "^1.4.4"
1256 },
1256 },
1257 "dependencies": {
1257 "dependencies": {
@@ -6419,8 +6419,7 @@
6419 },
6419 },
6420 "ansi-regex": {
6420 "ansi-regex": {
6421 "version": "2.1.1",
6421 "version": "2.1.1",
6422− "bundled": true,
6422+ "bundled": true
6423− "optional": true
6424 },
6423 },
6425 "aproba": {
6424 "aproba": {
6426 "version": "1.2.0",
6425 "version": "1.2.0",
@@ -6438,13 +6437,11 @@
6438 },
6437 },
6439 "balanced-match": {
6438 "balanced-match": {
6440 "version": "1.0.0",
6439 "version": "1.0.0",
6441− "bundled": true,
6440+ "bundled": true
6442− "optional": true
6443 },
6441 },
6444 "brace-expansion": {
6442 "brace-expansion": {
6445 "version": "1.1.11",
6443 "version": "1.1.11",
6446 "bundled": true,
6444 "bundled": true,
6447− "optional": true,
6448 "requires": {
6445 "requires": {
6449 "balanced-match": "^1.0.0",
6446 "balanced-match": "^1.0.0",
6450 "concat-map": "0.0.1"
6447 "concat-map": "0.0.1"
@@ -6457,18 +6454,15 @@
6457 },
6454 },
6458 "code-point-at": {
6455 "code-point-at": {
6459 "version": "1.1.0",
6456 "version": "1.1.0",
6460− "bundled": true,
6457+ "bundled": true
6461− "optional": true
6462 },
6458 },
6463 "concat-map": {
6459 "concat-map": {
6464 "version": "0.0.1",
6460 "version": "0.0.1",
6465− "bundled": true,
6461+ "bundled": true
6466− "optional": true
6467 },
6462 },
6468 "console-control-strings": {
6463 "console-control-strings": {
6469 "version": "1.1.0",
6464 "version": "1.1.0",
6470− "bundled": true,
6465+ "bundled": true
6471− "optional": true
6472 },
6466 },
6473 "core-util-is": {
6467 "core-util-is": {
6474 "version": "1.0.2",
6468 "version": "1.0.2",
@@ -6571,8 +6565,7 @@
6571 },
6565 },
6572 "inherits": {
6566 "inherits": {
6573 "version": "2.0.3",
6567 "version": "2.0.3",
6574− "bundled": true,
6568+ "bundled": true
6575− "optional": true
6576 },
6569 },
6577 "ini": {
6570 "ini": {
6578 "version": "1.3.5",
6571 "version": "1.3.5",
@@ -6582,7 +6575,6 @@
6582 "is-fullwidth-code-point": {
6575 "is-fullwidth-code-point": {
6583 "version": "1.0.0",
6576 "version": "1.0.0",
6584 "bundled": true,
6577 "bundled": true,
6585− "optional": true,
6586 "requires": {
6578 "requires": {
6587 "number-is-nan": "^1.0.0"
6579 "number-is-nan": "^1.0.0"
6588 }
6580 }
@@ -6595,20 +6587,17 @@
6595 "minimatch": {
6587 "minimatch": {
6596 "version": "3.0.4",
6588 "version": "3.0.4",
6597 "bundled": true,
6589 "bundled": true,
6598− "optional": true,
6599 "requires": {
6590 "requires": {
6600 "brace-expansion": "^1.1.7"
6591 "brace-expansion": "^1.1.7"
6601 }
6592 }
6602 },
6593 },
6603 "minimist": {
6594 "minimist": {
6604 "version": "0.0.8",
6595 "version": "0.0.8",
6605− "bundled": true,
6596+ "bundled": true
6606− "optional": true
6607 },
6597 },
6608 "minipass": {
6598 "minipass": {
6609 "version": "2.2.4",
6599 "version": "2.2.4",
6610 "bundled": true,
6600 "bundled": true,
6611− "optional": true,
6612 "requires": {
6601 "requires": {
6613 "safe-buffer": "^5.1.1",
6602 "safe-buffer": "^5.1.1",
6614 "yallist": "^3.0.0"
6603 "yallist": "^3.0.0"
@@ -6625,7 +6614,6 @@
6625 "mkdirp": {
6614 "mkdirp": {
6626 "version": "0.5.1",
6615 "version": "0.5.1",
6627 "bundled": true,
6616 "bundled": true,
6628− "optional": true,
6629 "requires": {
6617 "requires": {
6630 "minimist": "0.0.8"
6618 "minimist": "0.0.8"
6631 }
6619 }
@@ -6698,8 +6686,7 @@
6698 },
6686 },
6699 "number-is-nan": {
6687 "number-is-nan": {
6700 "version": "1.0.1",
6688 "version": "1.0.1",
6701− "bundled": true,
6689+ "bundled": true
6702− "optional": true
6703 },
6690 },
6704 "object-assign": {
6691 "object-assign": {
6705 "version": "4.1.1",
6692 "version": "4.1.1",
@@ -6709,7 +6696,6 @@
6709 "once": {
6696 "once": {
6710 "version": "1.4.0",
6697 "version": "1.4.0",
6711 "bundled": true,
6698 "bundled": true,
6712− "optional": true,
6713 "requires": {
6699 "requires": {
6714 "wrappy": "1"
6700 "wrappy": "1"
6715 }
6701 }
@@ -6785,8 +6771,7 @@
6785 },
6771 },
6786 "safe-buffer": {
6772 "safe-buffer": {
6787 "version": "5.1.1",
6773 "version": "5.1.1",
6788− "bundled": true,
6774+ "bundled": true
6789− "optional": true
6790 },
6775 },
6791 "safer-buffer": {
6776 "safer-buffer": {
6792 "version": "2.1.2",
6777 "version": "2.1.2",
@@ -6816,7 +6801,6 @@
6816 "string-width": {
6801 "string-width": {
6817 "version": "1.0.2",
6802 "version": "1.0.2",
6818 "bundled": true,
6803 "bundled": true,
6819− "optional": true,
6820 "requires": {
6804 "requires": {
6821 "code-point-at": "^1.0.0",
6805 "code-point-at": "^1.0.0",
6822 "is-fullwidth-code-point": "^1.0.0",
6806 "is-fullwidth-code-point": "^1.0.0",
@@ -6834,7 +6818,6 @@
6834 "strip-ansi": {
6818 "strip-ansi": {
6835 "version": "3.0.1",
6819 "version": "3.0.1",
6836 "bundled": true,
6820 "bundled": true,
6837− "optional": true,
6838 "requires": {
6821 "requires": {
6839 "ansi-regex": "^2.0.0"
6822 "ansi-regex": "^2.0.0"
6840 }
6823 }
@@ -6873,13 +6856,11 @@
6873 },
6856 },
6874 "wrappy": {
6857 "wrappy": {
6875 "version": "1.0.2",
6858 "version": "1.0.2",
6876− "bundled": true,
6859+ "bundled": true
6877− "optional": true
6878 },
6860 },
6879 "yallist": {
6861 "yallist": {
6880 "version": "3.0.2",
6862 "version": "3.0.2",
6881− "bundled": true,
6863+ "bundled": true
6882− "optional": true
6883 }
6864 }
6884 }
6865 }
6885 },
6866 },
@@ -19611,7 +19592,7 @@
19611 "requires": {
19592 "requires": {
19612 "underscore": "1.8.3",
19593 "underscore": "1.8.3",
19613 "web3-core-helpers": "1.0.0-beta.33",
19594 "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"
19615 },
19596 },
19616 "dependencies": {
19597 "dependencies": {
19617 "websocket": {
19598 "websocket": {
src/bridge/components/Base.js+2
@@ -63,6 +63,7 @@ import ValidatedSigil from './ValidatedSigil';
63import ValidatedBlockie from './ValidatedBlockie';
63import ValidatedBlockie from './ValidatedBlockie';
64import Chevron from './icons/Chevron'
64import Chevron from './icons/Chevron'
65import ShowBlockie from './ShowBlockie'
65import ShowBlockie from './ShowBlockie'
66+import Passport from './Passport'
66
67
67// HOC validated form components
68// HOC validated form components
68
69
@@ -202,4 +203,5 @@ export {
202 Warning,
203 Warning,
203
204
204 Chevron,
205 Chevron,
206+ Passport
205};
207};
src/bridge/components/Header.js+20−11
@@ -1,7 +1,7 @@
1import React from 'react'
1import React from 'react'
2import { Button, Chevron } from '../components/Base'
2import { Button, Chevron } from '../components/Base'
3
3
4−import { renderRoute } from '../lib/router'
4+import { renderRoute, ROUTE_NAMES } from '../lib/router'
5import { isLast } from '../lib/lib'
5import { isLast } from '../lib/lib'
6
6
7const Crumbs = (props) => {
7const Crumbs = (props) => {
@@ -40,15 +40,24 @@ const Crumbs = (props) => {
40 return rendered
40 return rendered
41}
41}
42
42
43−const Header = (props) =>
43+const Header = (props) => {
44+ let showCrumbs = !props.routeCrumbs.contains(ROUTE_NAMES.INVITE_LOGIN)
45+
46+ if (showCrumbs) {
47+ return (
44− <div className={'flex items-center h-10'}>
48+ <div className={'flex items-center h-10'}>
45− <Crumbs
49+ <Crumbs
46− routeCrumbs={ props.routeCrumbs }
50+ routeCrumbs={ props.routeCrumbs }
47− skipRoute={ props.skipRoute }
51+ skipRoute={ props.skipRoute }
48− networkType={ props.networkType }
52+ networkType={ props.networkType }
49− wallet={ props.wallet }
53+ wallet={ props.wallet }
50− pointCursor={ props.pointCursor }
54+ pointCursor={ props.pointCursor }
51− />
55+ />
52− </div>
56+ </div>
57+ )
58+ } else {
59+ return <h2 className="mt-9">Bridge</h2>
60+ }
61+}
53
62
54export default Header
63export 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,
212 font-size: 8rem;
212 font-size: 8rem;
213}
213}
214
214
215+.lh-8 {
216+ line-height: 8rem;
217+}
218+
215.ls-1 > * {
219.ls-1 > * {
216 padding-bottom: 1rem !important;
220 padding-bottom: 1rem !important;
217}
221}
@@ -481,3 +485,52 @@ code {
481.block {
485.block {
482 display: block;
486 display: block;
483}
487}
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 @@
1import { Just, Nothing } from 'folktale/maybe'
1import { Just, Nothing } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3−import { Button } from '../components/Base'
4−import { InnerLabel, ValidatedSigil, PointInput, TicketInput } from '../components/Base'
3+import { InnerLabel, ValidatedSigil, PointInput,
5−import { Row, Col, H1, P } from '../components/Base'
4+ TicketInput, Button, Row, Col, H1, P, Passport } from '../components/Base'
6import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
5import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
7import * as ob from 'urbit-ob'
6import * as ob from 'urbit-ob'
7+import * as azimuth from 'azimuth-js'
8
8
9import { ROUTE_NAMES } from '../lib/router'
9import { 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'
11
14
12const placeholder = (len) => {
15const placeholder = (len) => {
13 let bytes = window.crypto.getRandomValues(new Uint8Array(len))
16 let bytes = window.crypto.getRandomValues(new Uint8Array(len))
@@ -18,21 +21,34 @@ const placeholder = (len) => {
18 return ob.hex2patq(hex)
21 return ob.hex2patq(hex)
19}
22}
20
23
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+
21class InviteLogin extends React.Component {
31class InviteLogin extends React.Component {
22
32
23 constructor(props) {
33 constructor(props) {
24 super(props)
34 super(props)
25
35
26 this.state = {
36 this.state = {
27− ticket: '',
37+ inviteTicket: '',
28− pointName: '',
38+ verifyTicket: '',
29− isUnlocking: false
39+ realPoint: Nothing(),
40+ realWallet: Nothing(),
41+ walletReady: false,
42+ continueReady: true,
43+ stage: INVITE_STAGES.INVITE_LOGIN
30 }
44 }
31
45
32 this.pointPlaceholder = placeholder(4)
46 this.pointPlaceholder = placeholder(4)
33 this.ticketPlaceholder = placeholder(12)
47 this.ticketPlaceholder = placeholder(12)
34
48
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)
36 this.navigateLogin = this.navigateLogin.bind(this)
52 this.navigateLogin = this.navigateLogin.bind(this)
37 }
53 }
38
54
@@ -41,72 +57,195 @@ class InviteLogin extends React.Component {
41 this.props.pushRoute(ROUTE_NAMES.NETWORK)
57 this.props.pushRoute(ROUTE_NAMES.NETWORK)
42 }
58 }
43
59
44− handleTicketInput(ticket) {
60+ handleInviteTicketInput(inviteTicket) {
45− this.setState({ ticket })
61+ this.setState({ inviteTicket })
46 }
62 }
47
63
48− async walletFromTicket(ticket, pointName) {
64+ handleVerifyTicketInput(verifyTicket) {
65+ this.setState({ verifyTicket })
66+ }
67+
68+ confirmWalletDownload() {
49 this.setState({
69 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
51 });
89 });
52
90
53− const uhdw = await urbitWalletFromTicket(ticket, '~zod');
91+ const addr = addressFromSecp256k1Public(inviteWallet.publicKey);
54− this.props.setUrbitWallet(Just(uhdw));
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+ }
55
113
56 this.setState({
114 this.setState({
57− isUnlocking: false
115+ realPoint: point,
116+ realWallet: Just(wallet)
58− });
117+ })
59− this.props.pushRoute(ROUTE_NAMES.INVITE_WALLET);
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+ )
60 }
212 }
61
213
62 render() {
214 render() {
63 const { wallet } = this.props
215 const { wallet } = this.props
64− const { ticket, pointName } = this.state
216+ const { inviteTicket } = this.state
65
217
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()
67
222
68 return (
223 return (
69 <Row>
224 <Row>
70− <Col className="col-md-4">
225+ <Col className="col-md-4 mt-">
71− <H1>{ 'Welcome' }</H1>
226+ {stageDisplay}
72−
227+ {stageText}
73− <TicketInput
228+ {ticketElem}
74− className='mono mt-8'
229+ {continueButton}
75− prop-size='md'
230+
76− prop-format='innerLabel'
231+ { this.state.stage === INVITE_STAGES.INVITE_LOGIN &&
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
232+ <Button
102− prop-type={`link`}
233+ prop-type={`link`}
103− prop-size={`sm`}
234+ prop-size={`sm`}
104− className={`block mt-4`}
235+ className={`block mt-4`}
105− onClick={this.navigateLogin} >
236+ onClick={this.navigateLogin} >
106− {`Log in`}
237+ {`Log in`}
107− </Button>
238+ </Button>
239+ }
108
240
109 </Col>
241 </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>
110 </Row>
249 </Row>
111 )
250 )
112 }
251 }
src/bridge/views/InviteWallet.js+30−130
@@ -14,20 +14,10 @@ import Actions from './Point/Actions'
14import { BRIDGE_ERROR } from '../lib/error'
14import { BRIDGE_ERROR } from '../lib/error'
15import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
15import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
16
16
17+import { generateWallet, downloadWallet } from '../lib/invite'
18+
17// for wallet generation
19// 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'
21import PaperCollateralRenderer from 'PaperCollateralRenderer'
20import 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;
31
21
32class InviteWallet extends React.Component {
22class InviteWallet extends React.Component {
33
23
@@ -46,7 +36,7 @@ class InviteWallet extends React.Component {
46 this.proceed = this.proceed.bind(this)
36 this.proceed = this.proceed.bind(this)
47 }
37 }
48
38
49− componentDidMount() {
39+ async componentDidMount() {
50 const { web3, wallet, contracts, setPointCursor } = this.props
40 const { web3, wallet, contracts, setPointCursor } = this.props
51
41
52 web3.chain(_ =>
42 web3.chain(_ =>
@@ -58,15 +48,17 @@ class InviteWallet extends React.Component {
58 console.log('incoming', incoming)
48 console.log('incoming', incoming)
59
49
60 let point = Nothing();
50 let point = Nothing();
51+ let wallet = Nothing();
52+
61 if (incoming.length > 0) {
53 if (incoming.length > 0) {
62 let pointNum = parseInt(incoming[0]);
54 let pointNum = parseInt(incoming[0]);
63 point = Just(pointNum);
55 point = Just(pointNum);
64− this.generateWallet(pointNum);
56+ wallet = await generateWallet(pointNum);
65 if (incoming.length > 1) {
57 if (incoming.length > 1) {
66 //TODO notify user "...and others / ticket reusable"
58 //TODO notify user "...and others / ticket reusable"
67 }
59 }
68 }
60 }
69− this.setState({loaded: true, point: point})
61+ this.setState({loaded: true, point: point, wallet: Just(wallet)})
70 })))
62 })))
71 }
63 }
72
64
@@ -74,91 +66,11 @@ class InviteWallet extends React.Component {
74 //
66 //
75 }
67 }
76
68
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() {
69+ async download() {
137− const zip = new JSZip();
70+ let result = await downloadWallet(this.state.paper)
138− //TODO the categories here aren't explained in bridge at all...
71+ this.setState({
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 });
72+ downloaded: true
161− });
73+ })
162 }
74 }
163
75
164 proceed() {
76 proceed() {
@@ -169,7 +81,6 @@ class InviteWallet extends React.Component {
169 }
81 }
170
82
171 render() {
83 render() {
172−
173 const { loaded, point, wallet, paper, downloaded } = this.state;
84 const { loaded, point, wallet, paper, downloaded } = this.state;
174
85
175 let loading;
86 let loading;
@@ -189,24 +100,24 @@ class InviteWallet extends React.Component {
189 );
100 );
190 }
101 }
191
102
192− let pointOverview;
103+ // let pointOverview;
193− if (loaded && Just.hasInstance(point)) {
104+ // if (loaded && Just.hasInstance(point)) {
194− const name = ob.patp(point.value);
105+ // const name = ob.patp(point.value);
195− const sigil = pour({
106+ // const sigil = pour({
196− patp: name,
107+ // patp: name,
197− renderer: ReactSVGComponents,
108+ // renderer: ReactSVGComponents,
198− size: 256
109+ // size: 256
199− });
110+ // });
200− pointOverview = (
111+ // pointOverview = (
201− //TODO Passport display component
112+ // //TODO Passport display component
202− <>
113+ // <>
203− <div className={'mt-12 pt-6'}>
114+ // <div className={'mt-12 pt-6'}>
204− { sigil }
115+ // { sigil }
205− </div>
116+ // </div>
206− <H3><code>{ name }</code></H3>
117+ // <H3><code>{ name }</code></H3>
207− </>
118+ // </>
208− );
119+ // );
209− }
120+ // }
210
121
211 let downloadButtonContents = 'Download wallet';
122 let downloadButtonContents = 'Download wallet';
212 if ( Nothing.hasInstance(this.state.wallet) ||
123 if ( Nothing.hasInstance(this.state.wallet) ||
@@ -267,18 +178,7 @@ class InviteWallet extends React.Component {
267 return (
178 return (
268 <Row>
179 <Row>
269 <Col>
180 <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−
280 { paperRenderer }
181 { paperRenderer }
281−
282 </Col>
182 </Col>
283 </Row>
183 </Row>
284 )
184 )
src/common/components/AppNavigation.js+1−1
@@ -24,7 +24,7 @@ class AppNavigation extends React.Component {
24 position: "fixed",
24 position: "fixed",
25 bottom: "10px",
25 bottom: "10px",
26 right: "10px",
26 right: "10px",
27− "text-decoration": "underline",
27+ textDecoration: "underline",
28 cursor: "pointer"
28 cursor: "pointer"
29 }
29 }
30
30