on e366ef3 help sign in
bridge/src/views/IssueChild.js
259 lines 7.1 kB history raw
1import { Just, Nothing } from 'folktale/maybe';
2import React from 'react';
3import { azimuth, ecliptic } from 'azimuth-js';
4import * as ob from 'urbit-ob';
5import * as need from '../lib/need';
7import {
8 Row,
9 Col,
10 H1,
11 P,
12 Anchor,
13 ShowBlockie,
14 ValidatedSigil,
15 PointInput,
16 AddressInput,
17 InnerLabel,
18} from '../components/old/Base';
20import StatelessTransaction from '../components/old/StatelessTransaction';
22import { NETWORK_TYPES } from '../lib/network';
23import { getSpawnCandidate } from '../lib/child';
24import { canDecodePatp } from '../lib/txn';
26import { isValidAddress } from '../lib/wallet';
27import { withNetwork } from '../store/network';
28import { compose } from '../lib/lib';
29import { withPointCursor } from '../store/pointCursor';
31const setFind = (set, pred) => {
32 for (const e of set) {
33 if (pred(e)) {
34 return e;
35 }
36 }
37 return undefined;
38};
40class IssueChild extends React.Component {
41 constructor(props) {
42 super(props);
44 const issuingPoint = parseInt(need.pointCursor(props), 10);
46 const getCandidate = () => ob.patp(getSpawnCandidate(issuingPoint));
48 const suggestions = [
49 getCandidate(),
50 getCandidate(),
51 getCandidate(),
52 getCandidate(),
53 ];
55 this.state = {
56 receivingAddress: '',
57 issuingPoint: issuingPoint,
58 desiredPoint: '',
59 isAvailable: Nothing(), // use Nothing to allow attempt when offline
60 suggestions: suggestions,
61 validChildren: new Set(),
62 autoComplete: '',
63 };
65 this.handlePointInput = this.handlePointInput.bind(this);
66 this.handleAddressInput = this.handleAddressInput.bind(this);
67 this.createUnsignedTxn = this.createUnsignedTxn.bind(this);
68 this.statelessRef = React.createRef();
69 }
71 handleAddressInput = receivingAddress => {
72 this.setState({ receivingAddress });
73 this.statelessRef.current.clearTxn();
74 };
76 componentWillMount() {
77 const { issuingPoint } = this.state;
79 const validContracts = need.contracts(this.props);
81 azimuth
82 .getUnspawnedChildren(validContracts, issuingPoint)
83 .then(ps => this.setState({ validChildren: new Set(ps.map(ob.patp)) }));
84 }
86 handlePointInput(desiredPoint) {
87 if (desiredPoint.length < 15) {
88 const suggestedPoint =
89 setFind(this.state.validChildren, e => e.startsWith(desiredPoint)) ||
90 '';
91 const rendered =
92 ' '.repeat(desiredPoint.length) +
93 suggestedPoint.substring(desiredPoint.length);
95 const available = Just(this.state.validChildren.has(desiredPoint));
97 this.setState({
98 desiredPoint,
99 isAvailable: available,
100 autoComplete: rendered,
101 });
102 this.statelessRef.current.clearTxn();
103 }
104 }
106 createUnsignedTxn() {
107 const { state, props } = this;
109 if (isValidAddress(state.receivingAddress) === false) return Nothing();
110 if (state.isAvailable === false) return Nothing();
111 if (canDecodePatp(state.desiredPoint) === false) return Nothing();
113 const validContracts = need.contracts(props);
115 const pointDec = ob.patp2dec(state.desiredPoint);
117 const txn = ecliptic.spawn(
118 validContracts,
119 pointDec,
120 state.receivingAddress
121 );
123 return Just(txn);
124 }
126 validatePoint = patp => {
127 const point = this.point;
129 let vpatp = false;
130 try {
131 vpatp = ob.isValidPatp(patp);
132 } catch (_) {}
134 let vchild = false;
135 try {
136 vchild = ob.sein(patp) === ob.patp(point);
137 } catch (_) {}
139 return vpatp && vchild;
140 };
142 render() {
143 const { props, state } = this;
145 const validAddress = isValidAddress(state.receivingAddress);
147 const canGenerate = props.web3.matchWith({
148 Nothing: () => {
149 return validAddress === true;
150 },
151 Just: () => {
152 return validAddress === true && state.isAvailable.value === true;
153 },
154 });
156 const esvisible =
157 props.networkType === NETWORK_TYPES.ROPSTEN ||
158 props.networkType === NETWORK_TYPES.MAINNET;
160 const esdomain =
161 props.networkType === NETWORK_TYPES.ROPSTEN
162 ? 'ropsten.etherscan.io'
163 : 'etherscan.io';
165 return (
166 <Row>
167 <Col>
168 <H1>
169 {'Issue a Child From '}{' '}
170 <code>{`${ob.patp(state.issuingPoint)}`}</code>
171 </H1>
173 <P>
174 {`Please enter the point you would like to issue, and specify the
175 receiving Ethereum address. If you need to create an address, you
176 can also use Wallet Generator.`}
177 </P>
179 <P>
180 {`Your point can only issue children with particular names. Some
181 valid suggestions for `}
182 {<code>{ob.patp(state.issuingPoint)}</code>}
183 {' are '}
184 <code>{state.suggestions[0]}</code>
185 {', '}
186 <code>{state.suggestions[1]}</code>
187 {', and '}
188 <code>{state.suggestions[2]}</code>
189 {'.'}
190 </P>
192 <PointInput
193 prop-size="lg"
194 prop-format="innerLabel"
195 className={'mono mt-8'}
196 placeholder={`e.g. ${state.suggestions[3]}`}
197 value={state.desiredPoint}
198 onChange={this.handlePointInput}>
199 <InnerLabel>{'Point to Issue'}</InnerLabel>
200 <div
201 style={{
202 marginTop: '8.8rem',
203 marginLeft: '4.6rem',
204 fontSize: '5rem',
205 color: '#757575',
206 whiteSpace: 'pre-wrap',
207 }}
208 className="abs tl-0 mono">
209 {state.autoComplete}
210 </div>
211 <ValidatedSigil
212 className={'tr-0 mt-05 mr-0 abs'}
213 patp={state.desiredPoint}
214 size={68}
215 margin={8}
216 validator={() => this.validatePoint(state.desiredPoint)}
217 />
218 </PointInput>
220 <AddressInput
221 className="text-mono mt-8"
222 prop-size="lg"
223 prop-format="innerLabel"
224 placeholder={`e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f`}
225 value={state.receivingAddress}
226 disabled={
227 Nothing.hasInstance(state.isAvailable) || !state.isAvailable.value
228 }
229 onChange={v => this.handleAddressInput(v)}>
230 <InnerLabel>{'Receiving Address'}</InnerLabel>
231 <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
232 </AddressInput>
234 <Anchor
235 className={'mt-1'}
236 prop-size={'sm'}
237 prop-disabled={
238 !isValidAddress(state.receivingAddress) || !esvisible
239 }
240 target={'_blank'}
241 href={`https://${esdomain}/address/${state.receivingAddress}`}>
242 {'View on Etherscan ↗'}
243 </Anchor>
245 <StatelessTransaction
246 canGenerate={canGenerate}
247 createUnsignedTxn={this.createUnsignedTxn}
248 ref={this.statelessRef}
249 />
250 </Col>
251 </Row>
252 );
253 }
256export default compose(
257 withNetwork,
258 withPointCursor
259)(IssueChild);