import Maybe from 'folktale/maybe' import React from 'react' import * as azimuth from 'azimuth-js' import * as ob from 'urbit-ob' import { Row, Col, H1, H3, P, Anchor, Warning } from '../components/Base' import { Button, ShowBlockie, ValidatedSigil } from '../components/Base' import { PointInput, AddressInput, InnerLabel } from '../components/Base' import StatelessTransaction from '../components/StatelessTransaction' import { ROUTE_NAMES } from '../lib/router' import { NETWORK_NAMES } from '../lib/network' import { BRIDGE_ERROR } from '../lib/error' import { getSpawnCandidate } from '../lib/child' import { canDecodePatp, sendSignedTransaction, fromWei, } from '../lib/txn' import { ETH_ZERO_ADDR, isValidAddress, eqAddr, addressFromSecp256k1Public } from '../lib/wallet' class IssueChild extends React.Component { constructor(props) { super(props) const issuingPoint = props.pointCursor.matchWith({ Just: (pt) => parseInt(pt.value, 10), Nothing: () => { throw BRIDGE_ERROR.MISSING_POINT } }) const getCandidate = () => ob.patp(getSpawnCandidate(issuingPoint)) const suggestions = [ getCandidate(), getCandidate(), getCandidate(), getCandidate() ] this.state = { txError: Maybe.Nothing(), receivingAddress: '', issuingPoint: issuingPoint, desiredPoint: '', isAvailable: Maybe.Nothing(), // allow attempt when offline userApproval: false, nonce: '', gasPrice: '5', chainId: '', gasLimit: '600000', txn: Maybe.Nothing(), stx: Maybe.Nothing(), suggestions: suggestions, } this.handlePointInput = this.handlePointInput.bind(this) this.handleAddressInput = this.handleAddressInput.bind(this) this.handleConfirmAvailability = this.handleConfirmAvailability.bind(this) // Transaction this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this) this.handleSubmit = this.handleSubmit.bind(this) this.handleSetUserApproval = this.handleSetUserApproval.bind(this) this.handleSetTxn = this.handleSetTxn.bind(this) this.handleSetStx = this.handleSetStx.bind(this) this.handleSetNonce = this.handleSetNonce.bind(this) this.handleSetChainId = this.handleSetChainId.bind(this) this.handleSetGasPrice = this.handleSetGasPrice.bind(this) this.handleSetGasLimit = this.handleSetGasLimit.bind(this) } componentDidMount() { const { props } = this const addr = props.wallet.matchWith({ Just: wal => addressFromSecp256k1Public(wal.value.publicKey), Nothing: () => { throw BRIDGE_ERROR.MISSING_WALLET } }) props.web3.matchWith({ Nothing: () => {}, Just: (w3) => { const validWeb3 = w3.value const getTxMetadata = [ validWeb3.eth.getTransactionCount(addr), validWeb3.eth.net.getId(), validWeb3.eth.getGasPrice() ]; Promise.all(getTxMetadata).then(r => { const txMetadata = { nonce: r[0], chainId: r[1], gasPrice: fromWei(r[2], 'gwei'), }; this.setState({...txMetadata}) }) } }); } handleAddressInput = (receivingAddress) => { this.setState({ receivingAddress }) this.handleClearTxn() } handlePointInput(desiredPoint) { if (desiredPoint.length < 15) { this.setState({ desiredPoint, isAvailable: Maybe.Nothing() }) this.handleClearTxn() } } handleConfirmAvailability() { this.confirmPointAvailability().then(r => { this.setState({ isAvailable: r, }) }) } handleCreateUnsignedTxn() { const txn = this.createUnsignedTxn() this.setState({ txn }) } handleSetUserApproval(){ const {state} = this this.setState({ userApproval: !state.userApproval }) } handleSetTxn(txn){ this.setState({ txn }) } handleSetStx(stx){ this.setState({ stx, userApproval: false, }) } handleSetNonce(nonce){ this.setState({ nonce }) this.handleClearStx() } handleSetChainId(chainId){ this.setState({ chainId }) this.handleClearStx() } handleSetGasPrice(gasPrice){ this.setState({ gasPrice }) this.handleClearStx() } handleSetGasLimit(gasLimit){ this.setState({ gasLimit }) this.handleClearStx() } handleClearStx() { this.setState({ userApproval: false, stx: Maybe.Nothing(), }) } handleClearTxn() { this.setState({ userApproval: false, txn: Maybe.Nothing(), stx: Maybe.Nothing(), }) } handleClearTransaction() { this.setState({ userApproval: false, txn: Maybe.Nothing(), stx: Maybe.Nothing(), }) } handleSubmit(){ const { props, state } = this sendSignedTransaction(props.web3.value, state.stx) .then(sent => { props.setTxnHashCursor(sent) props.popRoute() props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION) }) .catch(err => { this.setState({ txError: err.map(val => val.merge()) }) }) } async confirmPointAvailability() { const { state, props } = this if (canDecodePatp(state.desiredPoint) === false) return Maybe.Just(false) const validContracts = props.contracts.matchWith({ Just: (cs) => cs.value, Nothing: () => { throw BRIDGE_ERROR.MISSING_CONTRACTS } }) const pointDec = ob.patp2dec(state.desiredPoint) // This check prevents galaxies from creating planets, bc right now it results in a failed tx // The intention is for the Azimuth contract to allow this in the future someday soon // This size check will need to be removed at that time const prefixSize = azimuth.azimuth.getPointSize(azimuth.azimuth.getPrefix(pointDec)) if (prefixSize + 1 !== azimuth.azimuth.getPointSize(pointDec)) return Maybe.Just(false) const owner = await azimuth.azimuth.getOwner( validContracts, pointDec ) if (eqAddr(owner, ETH_ZERO_ADDR)) return Maybe.Just(true) return Maybe.Just(false) } createUnsignedTxn() { const { state, props } = this if (isValidAddress(state.receivingAddress) === false) return Maybe.Nothing() if (state.isAvailable === false) return Maybe.Nothing() if (canDecodePatp(state.desiredPoint) === false) return Maybe.Nothing() const validContracts = props.contracts.matchWith({ Just: (cs) => cs.value, Nothing: () => { throw BRIDGE_ERROR.MISSING_CONTRACTS } }) const pointDec = ob.patp2dec(state.desiredPoint) const txn = azimuth.ecliptic.spawn( validContracts, pointDec, state.receivingAddress ) return Maybe.Just(txn) } buttonTriState() { const a = this.state.isAvailable if (Maybe.Nothing.hasInstance(a)) return 'blue' if (a.value === false) return 'yellow' if (a.value === true) return 'green' } buttonTriStateText() { const a = this.state.isAvailable if (Maybe.Nothing.hasInstance(a)) return 'Confirm Availablility' if (a.value === false) return 'Point is Not Available' if (a.value === true) return 'Available' } validatePoint = patp => { const point = this.point let vpatp = false try { vpatp = ob.isValidPatp(patp) } catch(_) { } let vchild = false try { vchild = ob.sein(patp) === ob.patp(point) } catch(_) { } return vpatp && vchild } render() { const { props, state } = this const validAddress = isValidAddress(state.receivingAddress) const validPoint = canDecodePatp(state.desiredPoint) const canGenerate = props.web3.matchWith({ Nothing: () => { return validAddress === true && validPoint === true }, Just: () => { return validAddress === true && validPoint === true && state.isAvailable.value === true } }) const canSign = !Maybe.Nothing.hasInstance(state.txn) const canApprove = !Maybe.Nothing.hasInstance(state.stx) const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true const esvisible = props.networkType === NETWORK_NAMES.ROPSTEN || props.networkType === NETWORK_NAMES.MAINNET const esdomain = props.networkType === NETWORK_NAMES.ROPSTEN ? "ropsten.etherscan.io" : "etherscan.io" return (

{ 'Issue a Child From ' } { `${ob.patp(state.issuingPoint)}` }

{ `Please enter the point you would like to issue, and specify the receiving Ethereum address. If you need to create an address, you can also use Wallet Generator.` }

{ `Your point can only issue children with particular names. Some valid suggestions for ` } { { ob.patp(state.issuingPoint) } } { ' are ' } { state.suggestions[0] } { ', ' } { state.suggestions[1] } { ', and ' } { state.suggestions[2] } { '.' }

{ 'Point to Issue' } this.validatePoint(state.desiredPoint)} /> this.handleAddressInput(v) }> { 'Receiving Address' } {'View on Etherscan ↗'} { props.web3.matchWith({ Nothing: _ =>
, Just: _ => }) } { Maybe.Nothing.hasInstance(state.txError) ? '' :

{ 'There was an error sending your transaction.' }

{ state.txError.value }
} ) } } export default IssueChild