@@ -1,4 +1,4 @@
1−import Maybe from 'folktale/maybe'
1+import { Just, Nothing } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3import { pour } from 'sigil-js'
3import { pour } from 'sigil-js'
4import * as ob from 'urbit-ob'
4import * as ob from 'urbit-ob'
@@ -13,7 +13,22 @@ import Actions from './Point/Actions'
13import { BRIDGE_ERROR } from '../lib/error'
13import { BRIDGE_ERROR } from '../lib/error'
14import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
14import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
16+// for wallet generation
17+import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
18+import * as more from 'more-entropy'
19+import lodash from 'lodash'
20+import PaperCollateralRenderer from 'PaperCollateralRenderer'
21+import JSZip from 'jszip'
22+import saveAs from 'file-saver'
23+import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
24+ GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
27+ } from '../../walletgen/lib/constants'
28+const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
29+const NEXT_STEP_NUM = 6;
31+//TODO more like InviteGenerate...
17class InviteClaim extends React.Component {
32class InviteClaim extends React.Component {
19 constructor(props) {
34 constructor(props) {
@@ -21,10 +36,14 @@ class InviteClaim extends React.Component {
22 this.state = {
37 this.state = {
23 loaded: false,
38 loaded: false,
24− point: Maybe.Nothing(),
39+ point: Nothing(),
25− newTicket: Maybe.Nothing(),
40+ wallet: Nothing(),
26− isGenerating: true
41+ paper: Nothing(),
43+ //TODO remove bools in favor of Nothings/Justs
46+ this.download = this.download.bind(this)
30 componentDidMount() {
49 componentDidMount() {
@@ -38,14 +57,13 @@ class InviteClaim extends React.Component {
38 const incoming = await azimuth.azimuth.getTransferringFor(ctrcs, addr)
57 const incoming = await azimuth.azimuth.getTransferringFor(ctrcs, addr)
39 console.log('incoming', incoming)
58 console.log('incoming', incoming)
41− this.state.loaded = true;
60+ let point = Nothing();
42− if (0 === incoming.length) {
61+ if (incoming.length > 0) {
43− setPointCursor(Maybe.Nothing());
45− setPointCursor(Maybe.Just(incoming[0]));
62+ point = Just(incoming[0]);
46− this.generateWallet(incoming[0])
63+ this.generateWallet(incoming[0]);
47− if (1 < incoming.length) {
64+ this.setState({ loaded: true, point: point });
65+ if (incoming.length > 1) {
48− //TODO notify user "...and others"
66+ //TODO notify user "...and others / ticket reusable"
@@ -55,80 +73,205 @@ class InviteClaim extends React.Component {
58− generateWallet(point) {
59− //TODO combine wg/views/Generate and Download into single lib function?
76+ //TODO pulled from walletgen/views/Generate and Download, put into lib
77+ async generateWallet(point) {
78+ const makeTicket = point => {
80+ const bits = point < MIN_STAR
81+ ? GALAXY_ENTROPY_BITS
84+ : PLANET_ENTROPY_BITS
86+ const bytes = bits / 8
87+ const some = new Uint8Array(bytes)
88+ window.crypto.getRandomValues(some)
90+ const gen = new more.Generator()
92+ return new Promise((resolve, reject) => {
93+ gen.generate(bits, result => {
94+ const chunked = lodash.chunk(result, 2)
95+ const desired = chunked.slice(0, bytes) // only take required entropy
96+ const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1])
97+ const entropy = lodash.zipWith(some, more, (x, y) => x ^ y)
98+ const buf = Buffer.from(entropy)
99+ const patq = ob.hex2patq(buf.toString('hex'))
101+ reject('Entropy generation failed')
106+ const genWallet = async (point, ticket, cb) => {
108+ const boot = point < MIN_STAR || point > MAX_STAR;
112+ seedSize: SEED_LENGTH_BYTES,
119+ const wallet = await kg.generateWallet(config);
121+ // This is here to notify the anyone who opens console because the thread
122+ // hangs, blocking UI updates so this cannot be doen in the UI
123+ console.log('Generating Wallet for point address: ', point);
128+ const ticket = await makeTicket(point);
130+ const wallet = await genWallet(point, ticket);
132+ this.setState({ wallet: Just(wallet) });
133+ console.log('got wallet', wallet);
134+ console.log('should start rendering now');
138+ const zip = new JSZip();
139+ //TODO the categories here aren't explained in bridge at all...
141+ const bin0 = this.state.paper.filter(item => item.bin === '0');
142+ const bin1 = this.state.paper.filter(item => item.bin === '1');
143+ const bin2 = this.state.paper.filter(item => item.bin === '2');
144+ const bin3 = this.state.paper.filter(item => item.bin === '3');
145+ const bin4 = this.state.paper.filter(item => item.bin === '4');
147+ const bin0Folder = zip.folder("0. Public");
148+ const bin1Folder = zip.folder("1. Very High Friction Custody");
149+ const bin2Folder = zip.folder("2. High Friction Custody");
150+ const bin3Folder = zip.folder("3. Medium Friction Custody");
151+ const bin4Folder = zip.folder("4. Low Friction Custody");
153+ bin0.forEach(item => bin0Folder.file(`${item.pageTitle}.png`, item.png))
154+ bin1.forEach(item => bin1Folder.file(`${item.pageTitle}.png`, item.png));
155+ bin2.forEach(item => bin2Folder.file(`${item.pageTitle}.png`, item.png));
156+ bin3.forEach(item => bin3Folder.file(`${item.pageTitle}.png`, item.png));
157+ bin4.forEach(item => bin4Folder.file(`${item.pageTitle}.png`, item.png));
159+ zip.generateAsync({type:"blob"}).then((content) => {
160+ saveAs(content, 'urbit-wallet.zip');
161+ this.setState({ downloaded: true });
62 render() {
165 render() {
64− const { pointCursor } = this.props
167+ const { loaded, point, wallet, paper, downloaded } = this.state;
66− //TODO do something about DOM duplication below
169+ let loading;
171+ loading = ( <div>{ 'Loading...' }</div> );
68− if (!this.state.loaded) {
174+ let balanceWarning;
69− return (
175+ if (loaded && Nothing.hasInstance(point)) {
70− <Row>
176+ balanceWarning = (
72− {'Loading...'}
178+ <h3 className={'mb-2'}>{'Warning'}</h3>
74− </Row>
180+ 'This invite has no claimable balance.'
78− if (!Maybe.Just.hasInstance(pointCursor)) {
187+ if (loaded && Just.hasInstance(point)) {
79− return (
188+ const name = ob.patp(point.value);
80− <Row>
189+ const sigil = pour({
81− <Col>
190+ patp: name,
82− <Warning>
191+ renderer: ReactSVGComponents,
195+ //TODO Passport display component
83− <h3 className={'mb-2'}>{'Warning'}</h3>
197+ <div className={'mt-12 pt-6'}>
85− 'This invite has no claimable balance.'
199+ </div>
86− }
200+ <H3><code>{ name }</code></H3>
205+ let downloadButtonContents = 'Download wallet';
206+ if ( Nothing.hasInstance(this.state.wallet) ||
207+ Nothing.hasInstance(this.state.paper) ) {
208+ downloadButtonContents = (
210+ <span className="btn-spinner"></span>
211+ {'Generating wallet...'}
94− const point = pointCursor.matchWith({
216+ let downloadButton;
95− Just: (cursor) => cursor.value,
217+ let nextButton;
96− Nothing: () => {
218+ if (loaded) {
97− throw BRIDGE_ERROR.MISSING_POINT
219+ downloadButton = (
222+ prop-size={'lg wide'}
223+ disabled={( Nothing.hasInstance(this.state.wallet) ||
224+ Nothing.hasInstance(this.state.paper) )}
225+ onClick={this.download}
227+ <span className="relative">
228+ { downloadButtonContents }
101− const name = ob.patp(point)
233+ nextButton = (
236+ prop-size={'lg wide'}
237+ disabled={!this.state.downloaded}
238+ onClick={() => console.log('TODO route to next')}
103− const sigil = pour({
245+ let paperRenderer;
104− patp: name,
246+ if ( Just.hasInstance(this.state.wallet) &&
105− renderer: ReactSVGComponents,
247+ Nothing.hasInstance(this.state.paper) ) {
106− size: 256
248+ console.log('rendering for', point.value)
250+ <PaperCollateralRenderer
251+ wallet={{[point.value]: wallet.value}}
252+ className={'extremely-hidden'}
254+ console.log('got paper', data);
255+ this.setState({isRendering: false, paper: data})
257+ mode={'REGISTRATION'} />
112− <div className={'mt-12 pt-6'}>
264+
113− { sigil }
265+ { loading }
114− </div>
266+ { balanceWarning }
115− <H1><code>{ name }</code></H1>
267+ { pointOverview }
117− <p>{'TODO copy'}</p>
269+ <p>{'TODO copy. you need a wallet to receive the point into. download it below.'}</p>
119− <Button
271+ { downloadButton }
120− className={'mt-8'}
272+ { nextButton }
121− prop-size={'lg wide'}
273+
122− disabled={this.state.isGenerating}
274+ { paperRenderer }
123− onClick={() => console.log('TODO download wallet')}>
125− <span className="relative">
126− {this.state.isGenerating &&
127− <span className="btn-spinner"></span>
129− {'Generating wallet...'}