@@ -1,6 +1,6 @@
1−import Maybe from 'folktale/maybe'
1+import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
3−import * as azimuth from 'azimuth-js'
3+import { azimuth, ecliptic } from 'azimuth-js'
44import * as ob from 'urbit-ob'
55
66import { Row, Col, H1, H3, P, Anchor, Warning } from '../components/Base'
@@ -13,11 +13,7 @@ import { ROUTE_NAMES } from '../lib/router'
1313import { NETWORK_NAMES } from '../lib/network'
1414import { BRIDGE_ERROR } from '../lib/error'
1515import { getSpawnCandidate } from '../lib/child'
16−import {
17− canDecodePatp,
18− sendSignedTransaction,
19− fromWei,
20− } from '../lib/txn'
16+import { canDecodePatp, sendSignedTransaction, fromWei } from '../lib/txn'
2117
2218import {
2319 ETH_ZERO_ADDR,
@@ -30,7 +26,6 @@ class IssueChild extends React.Component {
3026 constructor(props) {
3127 super(props)
3228
33−
3429 const issuingPoint = props.pointCursor.matchWith({
3530 Just: (pt) => parseInt(pt.value, 10),
3631 Nothing: () => {
@@ -48,25 +43,24 @@ class IssueChild extends React.Component {
4843 ]
4944
5045 this.state = {
51− txError: Maybe.Nothing(),
46+ txError: Nothing(),
5247 receivingAddress: '',
5348 issuingPoint: issuingPoint,
5449 desiredPoint: '',
55− isAvailable: Maybe.Nothing(), // allow attempt when offline
50+ isAvailable: Nothing(), // use Nothing to allow attempt when offline
5651 userApproval: false,
5752 nonce: '',
5853 gasPrice: '5',
5954 chainId: '',
6055 gasLimit: '600000',
61− txn: Maybe.Nothing(),
62− stx: Maybe.Nothing(),
56+ txn: Nothing(),
57+ stx: Nothing(),
6358 suggestions: suggestions,
6459 }
6560
6661 this.handlePointInput = this.handlePointInput.bind(this)
6762 this.handleAddressInput = this.handleAddressInput.bind(this)
6863 this.handleConfirmAvailability = this.handleConfirmAvailability.bind(this)
69− // Transaction
7064 this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
7165 this.handleSubmit = this.handleSubmit.bind(this)
7266 this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
@@ -97,20 +91,20 @@ class IssueChild extends React.Component {
9791 validWeb3.eth.getTransactionCount(addr),
9892 validWeb3.eth.net.getId(),
9993 validWeb3.eth.getGasPrice()
100− ];
94+ ]
10195
10296 Promise.all(getTxMetadata).then(r => {
10397 const txMetadata = {
10498 nonce: r[0],
10599 chainId: r[1],
106100 gasPrice: fromWei(r[2], 'gwei'),
107− };
101+ }
108102
109103 this.setState({...txMetadata})
110104
111105 })
112106 }
113− });
107+ })
114108
115109 }
116110
@@ -127,7 +121,7 @@ class IssueChild extends React.Component {
127121 if (desiredPoint.length < 15) {
128122 this.setState({
129123 desiredPoint,
130− isAvailable: Maybe.Nothing()
124+ isAvailable: Nothing()
131125 })
132126 this.handleClearTxn()
133127 }
@@ -205,7 +199,7 @@ class IssueChild extends React.Component {
205199 handleClearStx() {
206200 this.setState({
207201 userApproval: false,
208− stx: Maybe.Nothing(),
202+ stx: Nothing(),
209203 })
210204 }
211205
@@ -214,8 +208,8 @@ class IssueChild extends React.Component {
214208 handleClearTxn() {
215209 this.setState({
216210 userApproval: false,
217− txn: Maybe.Nothing(),
218− stx: Maybe.Nothing(),
211+ txn: Nothing(),
212+ stx: Nothing(),
219213 })
220214 }
221215
@@ -224,8 +218,8 @@ class IssueChild extends React.Component {
224218 handleClearTransaction() {
225219 this.setState({
226220 userApproval: false,
227− txn: Maybe.Nothing(),
228− stx: Maybe.Nothing(),
221+ txn: Nothing(),
222+ stx: Nothing(),
229223 })
230224 }
231225
@@ -244,40 +238,48 @@ class IssueChild extends React.Component {
244238 })
245239 }
246240
247−
248−
249241 async confirmPointAvailability() {
250− const { state, props } = this
251−
252− if (canDecodePatp(state.desiredPoint) === false) return Maybe.Just(false)
242+ const { contracts } = this.props
243+ const { desiredPoint } = this.state
253244
254− const validContracts = props.contracts.matchWith({
255− Just: (cs) => cs.value,
256− Nothing: () => {
245+ const validContracts = contracts.matchWith({
246+ Just: cs => cs.value,
247+ Nothing: _ => {
257248 throw BRIDGE_ERROR.MISSING_CONTRACTS
258249 }
259250 })
260251
261− const pointDec = ob.patp2dec(state.desiredPoint)
252+ if (canDecodePatp(desiredPoint) === false) {
253+ return Just(false)
254+ }
262255
263− const owner = await azimuth.azimuth.getOwner(
264− validContracts,
265− pointDec
266− )
256+ const point = ob.patp2dec(desiredPoint)
257+ const pointSize = azimuth.getPointSize(point)
267258
268− if (eqAddr(owner, ETH_ZERO_ADDR)) return Maybe.Just(true)
259+ const prefix = azimuth.getPrefix(point)
260+ const prefixSize = azimuth.getPointSize(prefix)
269261
270− return Maybe.Just(false)
271− }
262+ // NB (jtobin): this prevents galaxies from attempting to spawn planets
263+ //
264+ if (pointSize !== prefixSize + 1) {
265+ return Just(false)
266+ }
272267
268+ const owner = await azimuth.getOwner(validContracts, point)
273269
270+ if (eqAddr(owner, ETH_ZERO_ADDR)) {
271+ return Just(true)
272+ }
273+
274+ return Just(false)
275+ }
274276
275277 createUnsignedTxn() {
276278 const { state, props } = this
277279
278− if (isValidAddress(state.receivingAddress) === false) return Maybe.Nothing()
279− if (state.isAvailable === false) return Maybe.Nothing()
280− if (canDecodePatp(state.desiredPoint) === false) return Maybe.Nothing()
280+ if (isValidAddress(state.receivingAddress) === false) return Nothing()
281+ if (state.isAvailable === false) return Nothing()
282+ if (canDecodePatp(state.desiredPoint) === false) return Nothing()
281283
282284 const validContracts = props.contracts.matchWith({
283285 Just: (cs) => cs.value,
@@ -288,20 +290,20 @@ class IssueChild extends React.Component {
288290
289291 const pointDec = ob.patp2dec(state.desiredPoint)
290292
291− const txn = azimuth.ecliptic.spawn(
293+ const txn = ecliptic.spawn(
292294 validContracts,
293295 pointDec,
294296 state.receivingAddress
295297 )
296298
297− return Maybe.Just(txn)
299+ return Just(txn)
298300 }
299301
300302
301303
302304 buttonTriState() {
303305 const a = this.state.isAvailable
304− if (Maybe.Nothing.hasInstance(a)) return 'blue'
306+ if (Nothing.hasInstance(a)) return 'blue'
305307 if (a.value === false) return 'yellow'
306308 if (a.value === true) return 'green'
307309 }
@@ -310,7 +312,7 @@ class IssueChild extends React.Component {
310312
311313 buttonTriStateText() {
312314 const a = this.state.isAvailable
313− if (Maybe.Nothing.hasInstance(a)) return 'Confirm Availablility'
315+ if (Nothing.hasInstance(a)) return 'Confirm Availablility'
314316 if (a.value === false) return 'Point is Not Available'
315317 if (a.value === true) return 'Available'
316318 }
@@ -352,9 +354,11 @@ class IssueChild extends React.Component {
352354 }
353355 })
354356
355− const canSign = !Maybe.Nothing.hasInstance(state.txn)
356− const canApprove = !Maybe.Nothing.hasInstance(state.stx)
357− const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
357+ const canSign = Just.hasInstance(state.txn)
358+ const canApprove = Just.hasInstance(state.stx)
359+ const canSend =
360+ Just.hasInstance(state.stx)
361+ && state.userApproval === true
358362
359363 const esvisible =
360364 props.networkType === NETWORK_NAMES.ROPSTEN ||
@@ -494,7 +498,7 @@ class IssueChild extends React.Component {
494498 handleSubmit={this.handleSubmit} />
495499
496500 {
497− Maybe.Nothing.hasInstance(state.txError)
501+ Nothing.hasInstance(state.txError)
498502 ? ''
499503 : <Warning className={'mt-8'}>
500504 <H3 style={{marginTop: 0, paddingTop: 0}}>