on master help sign in
Merge pull request #61 from urbit/jt-issue-60

Clean up StatelessTransaction

commit915baeb1b495e05698d54a08fced997ae01f2948 authorJared Tobin <jared@jtobin.io> dateTue 26 Feb 2019, 14:51 (8 years ago) parent0dcd5e83e05312
5 files changed+192−246
package.json+2−3 src/components/StatelessTransaction.js+164−157 src/lib/ledger.js+1−1 src/lib/txn.js+5−5 src/views/AcceptTransfer.js+20−80
package.json+2−3
@@ -58,11 +58,10 @@
5858 "pilot:cleanup": "pkill -f ganache-cli"
5959 },
6060 "devDependencies": {
61− "@ledgerhq/hw-transport-node-hid": "^4.24.0",
6261 "ganache-cli": "^6.2.1",
6362 "npm-run-all": "^4.1.3",
64− "truffle": "4.1.11",
65− "serve": "10.1.1"
63+ "serve": "10.1.1",
64+ "truffle": "4.1.11"
6665 },
6766 "browserslist": [
6867 ">0.2%",
src/components/StatelessTransaction.js+164−157
@@ -1,177 +1,184 @@
1−import Maybe from 'folktale/maybe'
1+import { Nothing } from 'folktale/maybe'
22import React from 'react'
33
44import { Code, H3 } from './Base'
55import { Button } from './Base'
6−import { CheckboxButton, Input, InnerLabel } from './Base'
7−
8−import {
9− renderSignedTx,
10− // createUnsignedTxn,
11− signTransaction,
12−} from '../lib/txn';
13−
6+import { CheckboxButton, Input, InnerLabel } from './Base'
147
8+import { renderSignedTx, signTransaction } from '../lib/txn'
159
1610const StatelessTransaction = props => {
17−
18− const generateButtonColor = Maybe.Nothing.hasInstance(props.txn)
11+ const { web3, gasPrice, gasLimit, nonce, chainId } = props
12+ const { setNonce, setChainId, setGasLimit, setGasPrice } = props
13+ const { txn, stx, createUnsignedTxn } = props
14+ const { canSign, canGenerate, canApprove } = props
15+ const { setUserApproval, userApproval } = props
16+ const { canSend, handleSubmit } = props
17+
18+ const generateButtonColor =
19+ Nothing.hasInstance(txn)
1920 ? 'blue'
2021 : 'green'
2122
22− const signerButtonColor = Maybe.Nothing.hasInstance(props.stx)
23+ const signerButtonColor =
24+ Nothing.hasInstance(stx)
2325 ? 'blue'
2426 : 'green'
2527
26− return (
28+ const generateTxnButton =
29+ <Button
30+ className={ 'mt-8' }
31+ disabled={ !canGenerate }
32+ prop-color={ generateButtonColor }
33+ prop-size={ 'lg wide' }
34+ onClick={ createUnsignedTxn }
35+ >
36+ { 'Generate Transaction' }
37+ </Button>
38+
39+ const unsignedTxnDisplay = txn.matchWith({
40+ Nothing: _ => '',
41+ Just: tx =>
42+ <React.Fragment>
43+ <H3 className={ 'mt-8' }>
44+ { 'Unsigned Transaction' }
45+ </H3>
46+ <Code>
47+ { JSON.stringify(tx.value, null, 2) }
48+ </Code>
49+ </React.Fragment>
50+ })
51+
52+ const gasPriceDialogue =
53+ <Input
54+ className={ 'mono mt-4' }
55+ prop-size={ 'md' }
56+ prop-format={ 'innerLabel' }
57+ value={ gasPrice }
58+ onChange={ setGasPrice }
59+ >
60+ <InnerLabel>
61+ { 'Gas Price (gwei)' }
62+ </InnerLabel>
63+ </Input>
64+
65+ const gasLimitDialogue =
66+ <Input
67+ className={ 'mono mt-4' }
68+ prop-size={ 'md' }
69+ prop-format={ 'innerLabel' }
70+ value={ gasLimit }
71+ onChange={ setGasLimit }>
72+ <InnerLabel>
73+ { 'Gas Limit' }
74+ </InnerLabel>
75+ </Input>
76+
77+ const nonceDialogue =
78+ <Input
79+ className={ 'mono mt-4' }
80+ prop-size={ 'md' }
81+ prop-format={ 'innerLabel' }
82+ value={ nonce }
83+ onChange={ setNonce }
84+ >
85+ <InnerLabel>
86+ { 'Nonce' }
87+ </InnerLabel>
88+ </Input>
89+
90+ const chainDialogue =
91+ <Input
92+ className={ 'mono mt-4 mb-8' }
93+ prop-size={ 'md' }
94+ prop-format={ 'innerLabel' }
95+ value={ chainId }
96+ onChange={ setChainId }
97+ >
98+ <InnerLabel>
99+ { 'Chain ID' }
100+ </InnerLabel>
101+ </Input>
102+
103+ const onlineParamsDialogue = web3.matchWith({
104+ Just: _ => <div />,
105+ Nothing: _ =>
106+ <React.Fragment>
107+ { nonceDialogue }
108+ { chainDialogue }
109+ </React.Fragment>
110+ })
111+
112+ const signTxnButton =
113+ <Button
114+ disabled={ !canSign }
115+ className={ 'mt-8' }
116+ prop-size={ 'lg wide' }
117+ prop-color={ signerButtonColor }
118+ onClick={ () => signTransaction(props) }
119+ >
120+ { 'Sign Transaction' }
121+ </Button>
122+
123+ const signedTxnDisplay = stx.matchWith({
124+ Nothing: _ => '',
125+ Just: tx =>
126+ <React.Fragment>
127+ <H3 className={ 'mt-8' }>
128+ { 'Signed Transaction' }
129+ </H3>
130+ <Code>
131+ { JSON.stringify(renderSignedTx(tx.value), null, 2) }
132+ </Code>
133+ </React.Fragment>
134+ })
135+
136+ const confirmButton =
137+ <CheckboxButton
138+ className={ 'mt-8' }
139+ disabled={ !canApprove }
140+ onClick={ setUserApproval }
141+ state={ userApproval }
142+ >
143+ <div>
144+ { `I approve this transaction and wish to send.` }
145+ </div>
146+ </CheckboxButton>
147+
148+ const sendTxnButton =
149+ <Button
150+ prop-size={ 'xl wide' }
151+ className={ 'mt-8' }
152+ disabled={ !canSend }
153+ onClick={ handleSubmit }
154+ >
155+ { 'Send Transaction' }
156+ </Button>
157+
158+ const sendDialogue = web3.matchWith({
159+ Nothing: _ => '',
160+ Just: _ =>
27161 <React.Fragment>
162+ { confirmButton }
163+ { sendTxnButton }
164+ </React.Fragment>
165+ })
28166
29− <Button
30− prop-size={'lg wide'}
31− prop-color={generateButtonColor}
32− className={'mt-8'}
33− disabled={ !props.canGenerate }
34− onClick={ () => props.createUnsignedTxn()}>
35− { 'Generate Transaction' }
36− </Button>
37−
38−
39− {
40− props.txn.matchWith({
41− Nothing: () => '',
42− Just: (tx) => {
43− console.log(tx.value)
44− const rendered = tx.value
45− return (
46− <React.Fragment>
47− <H3 className={'mt-8'}>{'Unsigned Transaction'}</H3>
48− <Code> {JSON.stringify(rendered, null, 2)} </Code>
49− </React.Fragment>
50− )
51− }
52− })
53− }
54−
55−
56− <Input
57− className='mono mt-4'
58− prop-size='md'
59− prop-format='innerLabel'
60− value={ props.gasPrice }
61− onChange={ v => props.setGasPrice(v) }>
62− <InnerLabel>
63− { 'Gas Price (gwei)' }
64− </InnerLabel>
65− </Input>
66−
67− <Input
68− className='mono mt-4'
69− prop-size='md'
70− prop-format='innerLabel'
71− value={ props.gasLimit }
72− onChange={ v => props.setGasLimit(v) }>
73− <InnerLabel>
74− { 'Gas Limit' }
75− </InnerLabel>
76− </Input>
77−
78− {
79− props.web3.matchWith({
80− Just: () => <div />,
81− Nothing: () =>
82− <React.Fragment>
83− <Input
84− className='mono mt-4'
85− prop-size='md'
86− prop-format='innerLabel'
87− value={ props.nonce }
88− onChange={ v => props.setNonce(v) }>
89− <InnerLabel>
90− { 'Nonce' }
91− </InnerLabel>
92− </Input>
93−
94− <Input
95− className='mono mt-4 mb-8'
96− prop-size='md'
97− prop-format='innerLabel'
98− value={ props.chainId }
99− onChange={ v => props.setChainId(v) }>
100− <InnerLabel>
101− { 'Chain ID' }
102− </InnerLabel>
103− </Input>
104− </React.Fragment>
105− })
106− }
107−
108− <Button
109− disabled={ !props.canSign }
110− className={'mt-8'}
111− prop-size={'lg wide'}
112− prop-color={signerButtonColor}
113− onClick={ () => signTransaction({
114− wallet: props.wallet,
115− walletType: props.walletType,
116− walletHdPath: props.walletHdPath,
117− networkType: props.networkType,
118− txn: props.txn,
119− setStx: props.setStx,
120− nonce: props.nonce,
121− chainId: props.chainId,
122− gasPrice: props.gasPrice,
123− gasLimit: props.gasLimit,
124− }) }>
125− { 'Sign Transaction' }
126− </Button>
127−
128−
129− {
130− props.stx.matchWith({
131− Nothing: () => '',
132− Just: (tx) => {
133− const rendered = renderSignedTx(tx.value)
134− return (
135− <React.Fragment>
136− <H3 className={'mt-8'}>{'Signed Transaction'}</H3>
137− <Code> {JSON.stringify(rendered, null, 2)} </Code>
138− </React.Fragment>
139− )
140− }
141− })
142− }
143−
144− {
145− props.web3.matchWith({
146− Nothing: () => '',
147− Just: () => {
148− return (
149− <React.Fragment>
150− <CheckboxButton
151− className={'mt-8'}
152− disabled={ !props.canApprove }
153− onClick={ () => props.setUserApproval() }
154− state={props.userApproval}>
155− <div>
156− { `I approve this transaction and wish to send.` }
157− </div>
158− </CheckboxButton>
159− <Button
160− prop-size={'xl wide'}
161− className={'mt-8'}
162− disabled={ !props.canSend }
163− onClick={ () => props.handleSubmit() }>
164− { 'Send Transaction' }
165− </Button>
166− </React.Fragment>
167− )
168− }
169− })
170− }
167+ return (
168+ <React.Fragment>
169+ { generateTxnButton }
170+ { unsignedTxnDisplay }
171+
172+ { gasPriceDialogue }
173+ { gasLimitDialogue }
174+ { onlineParamsDialogue }
175+
176+ { signTxnButton }
177+
178+ { signedTxnDisplay }
179+ { sendDialogue }
171180 </React.Fragment>
172181 )
173182}
174183
175−
176−
177184export default StatelessTransaction
src/lib/ledger.js+1−1
@@ -11,8 +11,8 @@ const chopHdPrefix = str =>
1111const ledgerSignTransaction = async (txn, hdpath) => {
1212 const transport = await Transport.create()
1313 const eth = new Eth(transport)
14− const path = chopHdPrefix(hdpath)
1514
15+ const path = chopHdPrefix(hdpath)
1616 const serializedTx = txn.serialize().toString('hex')
1717 const sig = await eth.signTransaction(path, serializedTx)
1818
src/lib/txn.js+5−5
@@ -1,6 +1,6 @@
11import * as ob from 'urbit-ob'
2−import Maybe from 'folktale/maybe'
3−import Result from 'folktale/result'
2+import { Just } from 'folktale/maybe'
3+import { Ok, Error } from 'folktale/result'
44import Tx from 'ethereumjs-tx'
55import Web3 from 'web3'
66
@@ -134,7 +134,7 @@ const signTransaction = async config => {
134134 stx.sign(sec)
135135 }
136136
137− setStx(Maybe.Just(stx))
137+ setStx(Just(stx))
138138}
139139
140140const sendSignedTransaction = (web3, stx) => {
@@ -150,10 +150,10 @@ const sendSignedTransaction = (web3, stx) => {
150150 return new Promise((resolve, reject) => {
151151 web3.eth.sendSignedTransaction(serializedTx)
152152 .on('receipt', txn =>
153− resolve(Maybe.Just(Result.Ok(txn)))
153+ resolve(Just(Ok(txn)))
154154 )
155155 .on('error', err => {
156− reject(Maybe.Just(Result.Error(err.message)))
156+ reject(Just(Error(err.message)))
157157 })
158158 })
159159}
src/views/AcceptTransfer.js+20−80
@@ -1,4 +1,4 @@
1−import Maybe from 'folktale/maybe'
1+import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
33import { Row, Col, H1, H3, P, Warning } from '../components/Base'
44import { InnerLabel, AddressInput, ShowBlockie, Anchor } from '../components/Base'
@@ -9,17 +9,8 @@ import StatelessTransaction from '../components/StatelessTransaction'
99import { BRIDGE_ERROR } from '../lib/error'
1010import { NETWORK_NAMES } from '../lib/network'
1111import { ROUTE_NAMES } from '../lib/router'
12−
13−import {
14− sendSignedTransaction,
15− fromWei,
16− } from '../lib/txn'
17−
18−import {
19− isValidAddress,
20− addressFromSecp256k1Public,
21−} from '../lib/wallet'
22−
12+import { sendSignedTransaction, fromWei } from '../lib/txn'
13+import { isValidAddress, addressFromSecp256k1Public } from '../lib/wallet'
2314
2415class AcceptTransfer extends React.Component {
2516 constructor(props) {
@@ -41,18 +32,18 @@ class AcceptTransfer extends React.Component {
4132
4233 this.state = {
4334 receivingAddress: receivingAddress,
44− txn: Maybe.Nothing(),
45− txError: Maybe.Nothing(),
35+ txn: Nothing(),
36+ txError: Nothing(),
4637 incomingPoint: incomingPoint,
4738 userApproval: false,
4839 nonce: '',
4940 gasPrice: '5',
5041 chainId: '',
5142 gasLimit: '600000',
52− stx: Maybe.Nothing(),
43+ stx: Nothing(),
5344 }
45+
5446 this.handleAddressInput = this.handleAddressInput.bind(this)
55− // Transaction
5647 this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
5748 this.handleSubmit = this.handleSubmit.bind(this)
5849 this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
@@ -97,57 +88,27 @@ class AcceptTransfer extends React.Component {
9788 })
9889 }
9990 });
100−
101− // const validPoint = props.pointCursor.matchWith({
102− // Just: (shp) => shp.value,
103− // Nothing: () => {
104− // throw BRIDGE_ERROR.MISSING_POINT
105− // }
106− // })
107−
108− // NB (jtobin):
109− //
110− // We need to get the point's owner, so it's not possible to perform this
111− // transaction offline.
112− //
113− // Perhaps we could add an 'owner' input in the offline case, if necessary.
114−
115− // const owner = props.pointCache[validPoint].owner
116− //
117− // const txn = Maybe.Just(this.createUnsignedTxn(
118− // owner,
119− // state.receivingAddress))
120− //
121− // this.setState({ txn })
12291 }
12392
124−
125−
12693 handleAddressInput(proxyAddress) {
12794 this.setState({ proxyAddress })
12895 this.handleClearTxn()
12996 }
13097
131−
13298 handleCreateUnsignedTxn() {
13399 const txn = this.createUnsignedTxn()
134100 this.setState({ txn })
135101 }
136102
137−
138103 handleSetUserApproval(){
139104 const {state} = this
140105 this.setState({ userApproval: !state.userApproval })
141106 }
142107
143−
144−
145108 handleSetTxn(txn){
146109 this.setState({ txn })
147110 }
148111
149−
150−
151112 handleSetStx(stx){
152113 this.setState({
153114 stx,
@@ -155,65 +116,41 @@ class AcceptTransfer extends React.Component {
155116 })
156117 }
157118
158−
159−
160119 handleSetNonce(nonce){
161120 this.setState({ nonce })
162121 this.handleClearStx()
163122 }
164123
165−
166−
167124 handleSetChainId(chainId){
168125 this.setState({ chainId })
169126 this.handleClearStx()
170127 }
171128
172−
173−
174129 handleSetGasPrice(gasPrice){
175130 this.setState({ gasPrice })
176131 this.handleClearStx()
177132 }
178133
179−
180−
181134 handleSetGasLimit(gasLimit){
182135 this.setState({ gasLimit })
183136 this.handleClearStx()
184137 }
185138
186−
187−
188139 handleClearStx() {
189140 this.setState({
190141 userApproval: false,
191− stx: Maybe.Nothing(),
142+ stx: Nothing(),
192143 })
193144 }
194145
195−
196−
197146 handleClearTxn() {
198147 this.setState({
199148 userApproval: false,
200− txn: Maybe.Nothing(),
201− stx: Maybe.Nothing(),
202− })
203− }
204−
205−
206−
207− handleClearTransaction() {
208− this.setState({
209− userApproval: false,
210− txn: Maybe.Nothing(),
211− stx: Maybe.Nothing(),
149+ txn: Nothing(),
150+ stx: Nothing(),
212151 })
213152 }
214153
215−
216−
217154 handleSubmit() {
218155 const { props, state } = this
219156 sendSignedTransaction(props.web3.value, state.stx)
@@ -223,8 +160,7 @@ class AcceptTransfer extends React.Component {
223160 props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
224161 })
225162 .catch(err => {
226− // Note that value.value is due to wrapped Maybe.Just + Result.Error
227− this.setState({ txError: Maybe.Just(err.value.value) })
163+ this.setState({ txError: err.map(val => val.merge()) })
228164 })
229165 }
230166
@@ -247,7 +183,7 @@ class AcceptTransfer extends React.Component {
247183
248184 const owner = props.pointCache[validPoint].owner
249185
250− return Maybe.Just(azimuth.ecliptic.transferFrom(
186+ return Just(azimuth.ecliptic.transferFrom(
251187 validContracts,
252188 owner,
253189 state.receivingAddress,
@@ -260,9 +196,11 @@ class AcceptTransfer extends React.Component {
260196 const { state, props } = this
261197 const validAddress = isValidAddress(state.receivingAddress)
262198 const canGenerate = validAddress === true
263− const canSign = !Maybe.Nothing.hasInstance(state.txn)
264− const canApprove = !Maybe.Nothing.hasInstance(state.stx)
265− const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
199+ const canSign = Just.hasInstance(state.txn)
200+ const canApprove = Just.hasInstance(state.stx)
201+ const canSend =
202+ Just.hasInstance(state.stx)
203+ && state.userApproval === true
266204
267205 const esvisible =
268206 props.networkType === NETWORK_NAMES.ROPSTEN ||
@@ -286,6 +224,7 @@ class AcceptTransfer extends React.Component {
286224 "as. But you can transfer to any address you like."
287225 }
288226 </P>
227+
289228 <AddressInput
290229 className='mono mt-8'
291230 prop-size='lg'
@@ -339,7 +278,7 @@ class AcceptTransfer extends React.Component {
339278 handleSubmit={this.handleSubmit} />
340279
341280 {
342− Maybe.Nothing.hasInstance(state.txError)
281+ Nothing.hasInstance(state.txError)
343282 ? ''
344283 : <Warning className={'mt-8'}>
345284 <H3 style={{marginTop: 0, paddingTop: 0}}>
@@ -350,6 +289,7 @@ class AcceptTransfer extends React.Component {
350289 { state.txError.value }
351290 </Warning>
352291 }
292+
353293 </Col>
354294 </Row>
355295 )