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