@@ -0,0 +1,262 @@
1+import { Just, Nothing } from 'folktale/maybe'
2+import React from 'react'
3+import { pour } from 'sigil-js'
4+import * as ob from 'urbit-ob'
5+import * as azimuth from 'azimuth-js'
6+
7+import {
8+ EthereumWallet,
9+ addressFromSecp256k1Public,
10+ urbitWalletFromTicket,
11+ addHexPrefix,
12+ WALLET_NAMES
13+} from '../lib/wallet'
14+
15+import ReactSVGComponents from '../components/ReactSVGComponents'
16+import KeysAndMetadata from './Point/KeysAndMetadata'
17+import Actions from './Point/Actions'
18+import { BRIDGE_ERROR } from '../lib/error'
19+import { sendMail } from '../lib/inviteMail'
20+import { Row, Col, Warning, Button, H1, H3,
21+ Input, InnerLabel
22+} from '../components/Base'
23+import StatelessTransaction from '../components/StatelessTransaction'
24+
25+// for wallet generation
26+import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
27+import * as more from 'more-entropy'
28+import lodash from 'lodash'
29+import { PLANET_ENTROPY_BITS } from '../../walletgen/lib/constants'
30+
31+// for transaction generation and signing
32+import {
33+ signTransaction,
34+ sendSignedTransaction,
35+ waitForTransactionConfirm,
36+ isTransactionConfirmed
37+} from '../lib/txn'
38+import * as tank from '../lib/tank'
39+import Tx from 'ethereumjs-tx'
40+
41+class InvitesSend extends React.Component {
42+
43+ constructor(props) {
44+ super(props)
45+
46+ this.state = {
47+ invitesAvailable: Nothing(),
48+ fromPool: Nothing(),
49+ haveInvited: Nothing(),
50+ randomPlanet: Nothing(),
51+ inviteWallet: Nothing(),
52+ targetEmail: ''
53+ }
54+
55+ this.findInvited = this.findInvited.bind(this);
56+ this.findRandomPlanet = this.findRandomPlanet.bind(this);
57+ this.generateWallet = this.generateWallet.bind(this);
58+ this.canGenerate = this.canGenerate.bind(this);
59+ this.handleEmailInput = this.handleEmailInput.bind(this);
60+ this.txnConfirmation = this.txnConfirmation.bind(this);
61+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this);
62+
63+ this.promptText = 'The email will be sent on first confirmation. Please wait.';
64+ }
65+
66+ componentDidMount() {
67+ this.point = this.props.pointCursor.matchWith({
68+ Just: (pt) => parseInt(pt.value, 10),
69+ Nothing: () => {
70+ throw BRIDGE_ERROR.MISSING_POINT
71+ }
72+ });
73+ this.contracts = this.props.contracts.matchWith({
74+ Just: cs => cs.value,
75+ Nothing: _ => {
76+ throw BRIDGE_ERROR.MISSING_CONTRACTS
77+ }
78+ });
79+
80+ azimuth.delegatedSending.getPool(this.contracts, this.point)
81+ .then(pool => {
82+ this.setState({fromPool: Just(pool)});
83+ azimuth.delegatedSending.invitesInPool(this.contracts, pool)
84+ .then(count => {
85+ this.setState({invitesAvailable: Just(count)});
86+ });
87+ });
88+ this.findInvited();
89+ this.findRandomPlanet();
90+ this.generateWallet();
91+ }
92+
93+ componentDidUpdate(prevProps) {
94+ //
95+ }
96+
97+ async findInvited() {
98+ let invited = await azimuth.delegatedSending.getInvited(
99+ this.contracts,
100+ this.point
101+ );
102+ invited = invited.map(async point => {
103+ const active = await azimuth.azimuth.isActive(this.contracts, point);
104+ console.log('point active', point, active);
105+ const res = {point: Number(point), active:active};
106+ return res;
107+ });
108+ invited = await Promise.all(invited);
109+ this.setState({haveInvited: Just(invited)});
110+ }
111+
112+ async findRandomPlanet() {
113+ let res = Nothing();
114+ const kids = await azimuth.azimuth.getUnspawnedChildren(
115+ this.contracts,
116+ azimuth.azimuth.getPrefix(this.point)
117+ );
118+ if (kids.length > 0) {
119+ const i = Math.floor(Math.random() * kids.length);
120+ res = Just(Number(kids[i]));
121+ }
122+ this.setState({randomPlanet: res});
123+ return res;
124+ }
125+
126+ //TODO pulled from walletgen/views/Generate, put into lib
127+ async generateWallet() {
128+ const makeTicket = () => {
129+
130+ const bits = PLANET_ENTROPY_BITS
131+
132+ const bytes = bits / 8
133+ const some = new Uint8Array(bytes)
134+ window.crypto.getRandomValues(some)
135+
136+ const gen = new more.Generator()
137+
138+ return new Promise((resolve, reject) => {
139+ gen.generate(bits, result => {
140+ const chunked = lodash.chunk(result, 2)
141+ const desired = chunked.slice(0, bytes) // only take required entropy
142+ const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1])
143+ const entropy = lodash.zipWith(some, more, (x, y) => x ^ y)
144+ const buf = Buffer.from(entropy)
145+ const patq = ob.hex2patq(buf.toString('hex'))
146+ resolve(patq)
147+ reject('Entropy generation failed')
148+ })
149+ })
150+ };
151+
152+ const ticket = await makeTicket(0x10000); // planet-sized ticket
153+ const wallet = await kg.generateWallet({ticket, ship:0});
154+ console.log('found ticket', ticket, wallet, wallet.ownership.keys.address);
155+ this.setState({inviteWallet:
156+ Just({ticket, owner:wallet.ownership.keys.address})
157+ });
158+ }
159+
160+ handleEmailInput(email) {
161+ //TODO simple .*@.*\..* email validation
162+ this.setState({ targetEmail: email });
163+ }
164+
165+ canGenerate() {
166+ //TODO and canSend()
167+ let res = this.state.invitesAvailable.matchWith({
168+ Nothing: () => false,
169+ Just: invites => ((invites.value > 0)
170+ && Just.hasInstance(this.state.randomPlanet)
171+ && Just.hasInstance(this.state.inviteWallet))
172+ });
173+ return res;
174+ }
175+
176+ createUnsignedTxn() {
177+ const txn = azimuth.delegatedSending.sendPoint(
178+ this.contracts,
179+ this.point,
180+ this.state.randomPlanet.value,
181+ this.state.inviteWallet.value.owner
182+ );
183+ return Just(txn)
184+ }
185+
186+ txnConfirmation(txHash, confirmations) {
187+ this.props.setTxnConfirmations(txHash, confirmations);
188+ if (confirmations === 1) {
189+ sendMail(this.state.targetEmail, this.state.inviteWallet.value.ticket)
190+ .then(console.log);
191+ }
192+ }
193+
194+ render() {
195+
196+ const inviteStatus = this.state.invitesAvailable.matchWith({
197+ Nothing: () => 'Loading...',
198+ Just: (invites) => {
199+ let res = `${invites.value} invites available`;
200+ return res + this.state.fromPool.matchWith({
201+ Nothing: () => '',
202+ Just: (pool) => (pool.value == this.point)
203+ ? ''
204+ : ` (from ${ob.patp(pool.value)})`
205+ });
206+ }
207+ });
208+
209+
210+ const inviteList = this.state.haveInvited.matchWith({
211+ Nothing: () => (<li>{'Loading...'}</li>),
212+ Just: (invited) => invited.value.map(i => {
213+ return (<li>
214+ <span class="invitee">{ob.patp(i.point)}</span>:
215+ <span class="status">{i.active ? 'accepted' : 'pending'}</span>
216+ </li>);
217+ })
218+ });
219+
220+ return (
221+ <Row>
222+ <Col>
223+
224+ <p>{ 'send invites here, for planets' }</p>
225+
226+ <p>{ inviteStatus }</p>
227+
228+ <ul>{ inviteList }</ul>
229+
230+ <Input
231+ value={ this.state.targetEmail }
232+ onChange={ this.handleEmailInput }>
233+ <InnerLabel>
234+ { 'Recipient email' }
235+ </InnerLabel>
236+ </Input>
237+
238+ <StatelessTransaction
239+ // Upper scope
240+ web3={this.props.web3}
241+ contracts={this.props.contracts}
242+ wallet={this.props.wallet}
243+ walletType={this.props.walletType}
244+ walletHdPath={this.props.walletHdPath}
245+ networkType={this.props.networkType}
246+ setTxnHashCursor={this.props.setTxnHashCursor}
247+ setTxnConfirmations={this.txnConfirmation}
248+ popRoute={this.props.popRoute}
249+ pushRoute={this.props.pushRoute}
250+ // Other
251+ promptText={this.promptText}
252+ canGenerate={this.canGenerate()}
253+ createUnsignedTxn={this.createUnsignedTxn}
254+ ref={this.statelessRef} />
255+
256+ </Col>
257+ </Row>
258+ )
259+ }
260+}
261+
262+export default InvitesSend