on 05c1914 help sign in
bridge/src/views/Point.js
363 lines 9.4 kB history raw
1import Maybe from 'folktale/maybe'
2import React from 'react'
3import { pour } from 'sigil-js'
4import * as ob from 'urbit-ob'
5import * as azimuth from 'azimuth-js'
7import PointList from '../components/PointList'
8import ReactSVGComponents from '../components/ReactSVGComponents'
9import KeysAndMetadata from './Point/KeysAndMetadata'
10import { BRIDGE_ERROR } from '../lib/error'
11import { ROUTE_NAMES } from '../lib/router'
12import { Row, Col, H1, H2, H3, P } from '../components/Base'
13import { Button } from '../components/Base'
14import {
15 ETH_ZERO_ADDR,
16 CURVE_ZERO_ADDR,
17 addressFromSecp256k1Public,
18 eqAddr
19 } from '../lib/wallet'
21const isPlanet = point =>
22 parseInt(point, 10) > 65535
24class Point extends React.Component {
26 constructor(props) {
27 super(props)
29 this.state = {
30 spawned: []
31 }
32 }
34 componentDidMount() {
35 this.cachePoints()
36 }
38 componentDidUpdate(prevProps) {
39 //
40 // NB (jtobin)
41 //
42 // When a link in the 'issued points' component is clicked, we don't
43 // actually leave the Point component, and componentDidMount will not fire
44 // again. But, we can force a call to cachePoints here.
45 //
46 const oldCursor = prevProps.pointCursor.getOrElse(true)
47 const newCursor = this.props.pointCursor.getOrElse(true)
49 if (oldCursor !== newCursor) {
50 this.cachePoints()
51 }
52 }
54 cachePoints() {
55 const { web3, contracts, pointCursor, addToPointCache } = this.props
57 web3.chain(_ =>
58 contracts.chain(ctrcs =>
59 pointCursor.chain(point => {
60 azimuth.azimuth.getPoint(ctrcs, point)
61 .then(details => addToPointCache({ [point]: details }))
62 this.updateSpawned(ctrcs, point)
63 })))
64 }
66 updateSpawned = contracts => {
67 const { pointCursor } = this.props
69 pointCursor.matchWith({
70 Nothing: _ => {},
71 Just: point => {
72 azimuth.azimuth.getSpawned(contracts, point.value)
73 .then(spawned => this.setState({ spawned }))
74 }
75 })
77 }
79 render() {
81 const { web3, popRoute, pushRoute, wallet } = this.props
82 const { setPointCursor } = this.props
83 const { pointCursor, pointCache } = this.props
85 const { spawned } = this.state
87 const addr = wallet.matchWith({
88 Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
89 Nothing: () => {
90 throw BRIDGE_ERROR.MISSING_WALLET
91 }
92 })
94 const point = pointCursor.matchWith({
95 Just: (cursor) => cursor.value,
96 Nothing: () => {
97 throw BRIDGE_ERROR.MISSING_POINT
98 }
99 })
101 const pointDetails =
102 point in pointCache
103 ? Maybe.Just(pointCache[point])
104 : Maybe.Nothing()
106 const name = ob.patp(point)
108 const sigil = pour({
109 patp: name,
110 renderer: ReactSVGComponents,
111 size: 256
112 })
114 const online = Maybe.Just.hasInstance(web3)
116 const canSetTransferProxy = pointDetails.matchWith({
117 // also should check is addr is operator
118 Nothing: _ => false,
119 Just: details => eqAddr(details.value.owner, addr)
120 })
122 const canSetSpawnProxy = pointDetails.matchWith({
123 Nothing: _ => false,
124 Just: details =>
125 eqAddr(details.value.owner, addr) && details.value.active
126 })
129 const canSetManagementProxy = pointDetails.matchWith({
130 Nothing: _ => false,
131 Just: details =>
132 eqAddr(details.value.owner, addr) && details.value.active
133 })
135 const canConfigureKeys = pointDetails.matchWith({
136 Nothing: _ => false,
137 Just: details => {
138 const canManage =
139 eqAddr(details.value.owner, addr)
140 || eqAddr(details.value.managementProxy, addr)
142 return canManage && details.value.active
143 }
144 })
146 const canIssueChild =
147 pointDetails.matchWith({
148 Nothing: () => false,
149 Just: (details) => {
150 const hasPermission =
151 eqAddr(details.value.owner, addr)
152 || eqAddr(details.value.spawnProxy, addr)
154 const isBooted =
155 details.value.keyRevisionNumber > 0
157 const isNotPlanet = !isPlanet(point)
159 return hasPermission && isBooted && isNotPlanet
160 }
161 })
163 const planet = isPlanet(point)
165 const canTransfer =
166 pointDetails.matchWith({
167 Nothing: () => false,
168 Just: (deets) =>
169 eqAddr(deets.value.transferProxy, addr) ||
170 eqAddr(deets.value.owner, addr)
171 })
173 const canGenKeyfile =
174 pointDetails.matchWith({
175 Nothing: () => false,
176 Just: (deets) => {
177 const hasPermission =
178 eqAddr(deets.value.owner, addr)
179 || eqAddr(deets.value.managementProxy, addr)
181 const isBooted =
182 deets.value.keyRevisionNumber > 0
184 return hasPermission && isBooted
185 }
186 })
188 const canAcceptTransfer =
189 pointDetails.matchWith({
190 Nothing: () => false,
191 Just: (deets) =>
192 eqAddr(deets.value.transferProxy, addr)
193 })
195 const canCancelTransfer =
196 pointDetails.matchWith({
197 Nothing: () => false,
198 Just: (deets) =>
199 eqAddr(deets.value.owner, addr) &&
200 !eqAddr(deets.value.transferProxy, ETH_ZERO_ADDR)
201 })
203 const routeHandler = route => {
204 popRoute()
205 pushRoute(route)
206 }
208 const issuedPointList =
209 spawned.length === 0
210 ? <div />
211 : <div>
212 <H3>{ 'Issued Points' }</H3>
214 <PointList
215 setPointCursor={ setPointCursor }
216 routeHandler={ routeHandler }
217 points={ spawned }
218 />
219 </div>
222 const displayReminder = pointDetails.matchWith({
223 Nothing: () => false,
224 Just: (deets) => {
225 return deets.value.encryptionKey === CURVE_ZERO_ADDR
226 && deets.value.authenticationKey === CURVE_ZERO_ADDR
227 }
228 })
230 return (
231 <Row>
232 <Col>
234 <div className={'mt-12 pt-6'}>
235 { sigil }
236 </div>
238 <H1><code>{ name }</code></H1>
240 <H2>{ 'Actions' }</H2>
242 {
243 displayReminder
244 ? <P>{`Before you can issue child points or generate your Arvo
245 keyfile, you need to set your public keys.`}</P>
246 : ''
247 }
249 <Row>
250 <Col className={'flex flex-column items-start col-md-4'}>
251 <Button
252 prop-size={'sm'}
253 prop-type={'link'}
254 disabled={ (online || planet) && !canIssueChild }
255 onClick={ () => {
256 pushRoute(ROUTE_NAMES.ISSUE_CHILD)
257 }}
258 >
259 { 'Issue child' }
260 </Button>
262 <Button
263 prop-size={'sm'}
264 prop-type={'link'}
265 disabled={ !canAcceptTransfer }
266 onClick={ () => {
267 pushRoute(ROUTE_NAMES.ACCEPT_TRANSFER)
268 }}
269 >
270 { 'Accept incoming transfer' }
271 </Button>
273 <Button
274 prop-size={'sm'}
275 prop-type={'link'}
276 disabled={ online && !canCancelTransfer }
277 onClick={ () => {
278 pushRoute(ROUTE_NAMES.CANCEL_TRANSFER)
279 }}
280 >
281 { 'Cancel outgoing transfer' }
282 </Button>
284 <Button
285 prop-size={'sm'}
286 prop-type={'link'}
287 disabled={ !canGenKeyfile }
288 onClick={ () => {
289 pushRoute(ROUTE_NAMES.GEN_KEYFILE)
290 }}
291 >
292 { 'Generate Arvo keyfile' }
293 </Button>
295 </Col>
296 <Col className={'flex flex-column items-start col-md-4'}>
298 <Button
299 disabled={ online && !canSetSpawnProxy }
300 prop-size={'sm'}
301 prop-type={'link'}
302 onClick={ () => {
303 pushRoute(ROUTE_NAMES.SET_SPAWN_PROXY)
304 }}
305 >
306 { 'Set spawn proxy' }
307 </Button>
309 <Button
310 disabled={ online && !canSetManagementProxy }
311 prop-size={'sm'}
312 prop-type={'link'}
313 onClick={ () => {
314 pushRoute(ROUTE_NAMES.SET_MANAGEMENT_PROXY)
315 }}
316 >
317 { 'Set management proxy' }
318 </Button>
320 <Button
321 disabled={ online && !canConfigureKeys }
322 prop-size={'sm'}
323 prop-type={'link'}
324 onClick={ () => {
325 pushRoute(ROUTE_NAMES.SET_KEYS)
326 }}
327 >
328 { 'Set public keys' }
329 </Button>
331 <Button
332 disabled={ online && !canTransfer }
333 prop-size={'sm'}
334 prop-type={'link'}
335 onClick={ () => {
336 pushRoute(ROUTE_NAMES.TRANSFER)
337 }}
338 >
339 { 'Transfer' }
340 </Button>
342 </Col>
343 </Row>
347 {
348 online
349 ? <KeysAndMetadata pointDetails={ pointDetails } />
350 : <div />
351 }
355 { issuedPointList }
357 </Col>
358 </Row>
359 )
360 }
363export default Point