on master help sign in
feat: implement inlineethereumtransaction
633e7b8Matt Condon (shrugs)
src/components/Buttons.js+1−1
@@ -7,7 +7,7 @@ export const ForwardButton = ({ loading, ...props }) => (
7 <Button accessory={loading ? <Blinky /> : '->'} {...props} />
7 <Button accessory={loading ? <Blinky /> : '->'} {...props} />
8);
8);
9export const DownloadButton = props => <Button accessory="↓" {...props} />;
9export const DownloadButton = props => <Button accessory="↓" {...props} />;
10−export const RestartButton = props => <Button accessory="↺" solid {...props} />;
10+export const RestartButton = props => <Button accessory="↺" {...props} />;
11export const GenerateButton = props => (
11export const GenerateButton = props => (
12 <Button accessory="○" solid {...props} />
12 <Button accessory="○" solid {...props} />
13);
13);
src/components/InlineEthereumTransaction.js+62−9
@@ -1,13 +1,19 @@
1import React from 'react';
1import React from 'react';
2+import {
3+ Grid,
4+ ToggleInput,
5+ ErrorText,
6+ Flex,
7+ LinkButton,
8+ H5,
2−import { Grid, ToggleInput, ErrorText } from 'indigo-react';
9+} from 'indigo-react';
3−import { GenerateButton, ForwardButton } from './Buttons';
10+import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
4import { useCheckboxInput } from 'lib/useInputs';
11import { useCheckboxInput } from 'lib/useInputs';
5import Blinky from './Blinky';
12import Blinky from './Blinky';
6
13
7export default function InlineEthereumTransaction({
14export default function InlineEthereumTransaction({
15+ // from useEthereumTransaction.bind
8 initializing,
16 initializing,
9− construct,
10− constructed,
11 canSign,
17 canSign,
12 generateAndSign,
18 generateAndSign,
13 signed,
19 signed,
@@ -17,7 +23,11 @@ export default function InlineEthereumTransaction({
17 reset,
23 reset,
18 error,
24 error,
19 setGasPrice,
25 setGasPrice,
26+ txHash,
27+
28+ // additional
20 className,
29 className,
30+ onReturn,
21}) {
31}) {
22 const [configureInput, { data: configurationOpen }] = useCheckboxInput({
32 const [configureInput, { data: configurationOpen }] = useCheckboxInput({
23 name: 'configure',
33 name: 'configure',
@@ -34,10 +44,29 @@ export default function InlineEthereumTransaction({
34 disabled: !signed,
44 disabled: !signed,
35 });
45 });
36
46
47+ // show receipt after successful broadcast
48+ const showReceipt = broadcasted || confirmed;
49+ // show configure controls pre-broadcast
50+ const showConfigureInput = !(broadcasted || confirmed);
51+ // show signed tx after signing
52+ const showSignedTx = signed;
53+
37 const renderButton = () => {
54 const renderButton = () => {
55+ if (error) {
56+ return (
57+ <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
58+ Reset Transaction
59+ </Grid.Item>
60+ );
38− if (confirmed) {
61+ } else if (confirmed) {
39− // some sort of next button or nothing at all
62+ return (
40− return null;
63+ <>
64+ <Grid.Divider />
65+ <Grid.Item full as={RestartButton} onClick={onReturn}>
66+ Return
67+ </Grid.Item>
68+ </>
69+ );
41 } else if (broadcasted) {
70 } else if (broadcasted) {
42 return (
71 return (
43 <Grid.Item
72 <Grid.Item
@@ -84,11 +113,35 @@ export default function InlineEthereumTransaction({
84 {error.message}
113 {error.message}
85 </Grid.Item>
114 </Grid.Item>
86 )}
115 )}
87− <Grid.Item full as={ToggleInput} {...configureInput} />
88
116
89− {!broadcasted && !confirmed && (
117+ {showConfigureInput && (
118+ <Grid.Item full as={ToggleInput} {...configureInput} />
119+ )}
120+
121+ {showSignedTx && (
90 <Grid.Item full as={ToggleInput} {...viewSignedTransaction} />
122 <Grid.Item full as={ToggleInput} {...viewSignedTransaction} />
91 )}
123 )}
124+
125+ {showReceipt && (
126+ <>
127+ <Grid.Divider />
128+ <Grid.Item full as={Flex} col className="pv4">
129+ <Flex.Item as={Flex} row justify="between">
130+ <Flex.Item as={H5}>Transaction Hash</Flex.Item>
131+ <Flex.Item as={LinkButton} href="https://google.com">
132+ Etherscan↗
133+ </Flex.Item>
134+ </Flex.Item>
135+ <Flex.Item as={Flex}>
136+ <Flex.Item flex as="code" className="f6 mono gray4 wrap">
137+ {txHash}
138+ </Flex.Item>
139+ <Flex.Item flex />
140+ </Flex.Item>
141+ </Grid.Item>
142+ <Grid.Divider />
143+ </>
144+ )}
92 </Grid>
145 </Grid>
93 );
146 );
94}
147}
src/components/old/StatelessTransaction.js+9−8
@@ -493,14 +493,15 @@ class StatelessTransaction extends React.Component {
493 className={'mt-8'}
493 className={'mt-8'}
494 prop-size={'lg wide'}
494 prop-size={'lg wide'}
495 prop-color={signerButtonColor}
495 prop-color={signerButtonColor}
496− onClick={() =>
496+ onClick={async () =>
497+ this.setStx(
497− signTransaction({
498+ await signTransaction({
498− ...this.props,
499+ ...this.props,
499− ...this.state,
500+ ...this.state,
500− setStx: this.setStx,
501− wallet: this.props.wallet.value,
501+ wallet: this.props.wallet.value,
502− txn: txn.value,
502+ txn: txn.value,
503− })
503+ })
504+ )
504 }>
505 }>
505 {'Sign Transaction'}
506 {'Sign Transaction'}
506 </Button>
507 </Button>
src/indigo-react/components/LinkButton.js+4−2
@@ -10,7 +10,7 @@ export default function LinkButton({
10 ...rest
10 ...rest
11}) {
11}) {
12 return (
12 return (
13− <span
13+ <As
14 onClick={!disabled && onClick ? onClick : undefined}
14 onClick={!disabled && onClick ? onClick : undefined}
15 style={{
15 style={{
16 ...(disabled && {
16 ...(disabled && {
@@ -26,8 +26,10 @@ export default function LinkButton({
26 },
26 },
27 className
27 className
28 )}
28 )}
29+ target="_blank"
30+ rel="noopener noreferrer"
29 {...rest}>
31 {...rest}>
30 {children}
32 {children}
31− </span>
33+ </As>
32 );
34 );
33}
35}
src/lib/txn.js−3
@@ -1,5 +1,4 @@
1import * as ob from 'urbit-ob';
1import * as ob from 'urbit-ob';
2−import { Just } from 'folktale/maybe';
3import Tx from 'ethereumjs-tx';
2import Tx from 'ethereumjs-tx';
4import { toWei, fromWei, toHex } from 'web3-utils';
3import { toWei, fromWei, toHex } from 'web3-utils';
5import retry from 'async-retry';
4import retry from 'async-retry';
@@ -24,7 +23,6 @@ const signTransaction = async config => {
24 walletHdPath,
23 walletHdPath,
25 networkType,
24 networkType,
26 txn,
25 txn,
27− setStx,
28 nonce,
26 nonce,
29 chainId,
27 chainId,
30 gasPrice,
28 gasPrice,
@@ -97,7 +95,6 @@ const signTransaction = async config => {
97 stx.sign(wallet.privateKey);
95 stx.sign(wallet.privateKey);
98 }
96 }
99
97
100− setStx(Just(stx));
101 return stx;
98 return stx;
102};
99};
103
100
src/lib/useEthereumTransaction.js+26−19
@@ -30,7 +30,16 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
30 const _web3 = need.web3(web3);
30 const _web3 = need.web3(web3);
31 const _wallet = need.wallet(wallet);
31 const _wallet = need.wallet(wallet);
32
32
33− const [error, setError] = useState();
33+ const [error, _setError] = useState();
34+ const setError = useCallback(
35+ error => {
36+ _setError(error);
37+ if (error) {
38+ console.error(error);
39+ }
40+ },
41+ [_setError]
42+ );
34 const [state, setState] = useState(STATE.NONE);
43 const [state, setState] = useState(STATE.NONE);
35 const [chainId, setChainId] = useState();
44 const [chainId, setChainId] = useState();
36 const [nonce, setNonce] = useState();
45 const [nonce, setNonce] = useState();
@@ -38,6 +47,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
38 const [gasLimit] = useState(initialGasLimit);
47 const [gasLimit] = useState(initialGasLimit);
39 const [unsignedTransaction, setUnsignedTransaction] = useState();
48 const [unsignedTransaction, setUnsignedTransaction] = useState();
40 const [signedTransaction, setSignedTransaction] = useState();
49 const [signedTransaction, setSignedTransaction] = useState();
50+ const [txHash, setTxHash] = useState();
41
51
42 const initializing = nonce === undefined || chainId === undefined;
52 const initializing = nonce === undefined || chainId === undefined;
43 const constructed = !!unsignedTransaction;
53 const constructed = !!unsignedTransaction;
@@ -55,6 +65,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
55
65
56 const generateAndSign = useCallback(async () => {
66 const generateAndSign = useCallback(async () => {
57 try {
67 try {
68+ setError(undefined);
58 // TODO: set varaibles
69 // TODO: set varaibles
59 // txn.gas = toHex(gasLimit);
70 // txn.gas = toHex(gasLimit);
60 // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
71 // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
@@ -64,8 +75,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
64 walletType,
75 walletType,
65 walletHdPath,
76 walletHdPath,
66 networkType,
77 networkType,
67− txn: Just(unsignedTransaction),
78+ txn: unsignedTransaction,
68− setStx: () => {}, // TODO: remove
69 nonce,
79 nonce,
70 chainId,
80 chainId,
71 gasPrice,
81 gasPrice,
@@ -84,25 +94,15 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
84 gasPrice,
94 gasPrice,
85 networkType,
95 networkType,
86 nonce,
96 nonce,
97+ setError,
87 unsignedTransaction,
98 unsignedTransaction,
88 walletHdPath,
99 walletHdPath,
89 walletType,
100 walletType,
90 ]);
101 ]);
91
102
92− const waitForConfirmation = useCallback(
93− async txHash => {
94− try {
95− await waitForTransactionConfirm(_web3, txHash);
96− setState(STATE.CONFIRMED);
97− } catch (error) {
98− setError(error);
99− }
100− },
101− [_web3]
102− );
103−
104 const broadcast = useCallback(async () => {
103 const broadcast = useCallback(async () => {
105 try {
104 try {
105+ setError(undefined);
106 const txHash = await sendSignedTransaction(
106 const txHash = await sendSignedTransaction(
107 _web3,
107 _web3,
108 signedTransaction,
108 signedTransaction,
@@ -110,22 +110,27 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
110 );
110 );
111
111
112 setState(STATE.BROADCASTED);
112 setState(STATE.BROADCASTED);
113− waitForConfirmation(txHash);
113+ setTxHash(txHash);
114+
115+ await waitForTransactionConfirm(_web3, txHash);
116+
117+ setState(STATE.CONFIRMED);
114 } catch (error) {
118 } catch (error) {
115 setError(error);
119 setError(error);
116 }
120 }
117− }, [_web3, signedTransaction, waitForConfirmation]);
121+ }, [_web3, setError, signedTransaction]);
118
122
119 const reset = useCallback(() => {
123 const reset = useCallback(() => {
120 setState(STATE.NONE);
124 setState(STATE.NONE);
121 setError(undefined);
125 setError(undefined);
122 setGasPrice(0);
126 setGasPrice(0);
123 setSignedTransaction(undefined);
127 setSignedTransaction(undefined);
124− }, [setState]);
128+ }, [setError]);
125
129
126 useEffect(() => {
130 useEffect(() => {
127 (async () => {
131 (async () => {
128 try {
132 try {
133+ setError(undefined);
129 const [nonce, chainId, estimatedGasPrice] = await Promise.all([
134 const [nonce, chainId, estimatedGasPrice] = await Promise.all([
130 _web3.eth.getTransactionCount(_wallet.address),
135 _web3.eth.getTransactionCount(_wallet.address),
131 _web3.eth.net.getId(),
136 _web3.eth.net.getId(),
@@ -139,7 +144,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
139 setError(error);
144 setError(error);
140 }
145 }
141 })();
146 })();
142− }, [_wallet.address, _web3.eth]);
147+ }, [_wallet.address, _web3.eth, setError]);
143
148
144 return {
149 return {
145 initializing,
150 initializing,
@@ -154,6 +159,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
154 reset,
159 reset,
155 error,
160 error,
156 inputsLocked,
161 inputsLocked,
162+ txHash,
157 bind: {
163 bind: {
158 constructed,
164 constructed,
159 canSign,
165 canSign,
@@ -165,6 +171,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
165 reset,
171 reset,
166 error,
172 error,
167 setGasPrice,
173 setGasPrice,
174+ txHash,
168 },
175 },
169 };
176 };
170}
177}
src/views/Activate/PassportTransfer.js+1
@@ -139,6 +139,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
139 full
139 full
140 className="mt3"
140 className="mt3"
141 as={RestartButton}
141 as={RestartButton}
142+ solid
142 onClick={goToRestart}>
143 onClick={goToRestart}>
143 Restart
144 Restart
144 </Grid.Item>
145 </Grid.Item>
src/views/Admin/AdminSetProxy.js+29−20
@@ -70,17 +70,8 @@ function useSetProxy(proxyType, address) {
70 );
70 );
71
71
72 const {
72 const {
73− initializing,
74− constructed,
75 construct: _construct,
73 construct: _construct,
76− constructAndSign,
77− generateAndSign,
78− sign,
79− signed,
80− broadcast,
81− broadcasted,
82 confirmed,
74 confirmed,
83− reset,
84 inputsLocked,
75 inputsLocked,
85 bind,
76 bind,
86 } = useEthereumTransaction(GAS_LIMITS.SET_PROXY);
77 } = useEthereumTransaction(GAS_LIMITS.SET_PROXY);
@@ -106,6 +97,7 @@ function useSetProxy(proxyType, address) {
106
97
107 return {
98 return {
108 construct,
99 construct,
100+ confirmed,
109 unset,
101 unset,
110 isUnsetting,
102 isUnsetting,
111 inputsLocked: inputsLocked || isUnsetting,
103 inputsLocked: inputsLocked || isUnsetting,
@@ -122,7 +114,9 @@ export default function AdminSetProxy() {
122
114
123 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
115 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
124
116
125− const { construct, unset, inputsLocked, bind } = useSetProxy(data.proxyType);
117+ const { construct, unset, inputsLocked, confirmed, bind } = useSetProxy(
118+ data.proxyType
119+ );
126 const [addressInput, { pass, data: address }] = useAddressInput({
120 const [addressInput, { pass, data: address }] = useAddressInput({
127 name: 'address',
121 name: 'address',
128 label: `New ${properProxyType} Address`,
122 label: `New ${properProxyType} Address`,
@@ -144,32 +138,47 @@ export default function AdminSetProxy() {
144 <Grid.Item full as={ViewHeader}>
138 <Grid.Item full as={ViewHeader}>
145 {properProxyType} Address
139 {properProxyType} Address
146 </Grid.Item>
140 </Grid.Item>
147− <Grid.Item full as={Text} className="mb4">
141+ <Grid.Item full as={Text} className="mb4 f5">
148 {proxyTypeToHumanDescription(data.proxyType)}
142 {proxyTypeToHumanDescription(data.proxyType)}
149 </Grid.Item>
143 </Grid.Item>
144+ <Grid.Item
145+ full
146+ as={Text}
150− <Grid.Item full as={Text} className="f6 mb1">
147+ className={cn('f6 mb1', {
148+ green3: confirmed,
149+ })}>
151− Current {properProxyType} Address
150+ {confirmed ? 'New' : 'Current'} {properProxyType} Address
152 </Grid.Item>
151 </Grid.Item>
153 <Grid.Item full as={Flex} row justify="between" align="center">
152 <Grid.Item full as={Flex} row justify="between" align="center">
154 <Flex.Item
153 <Flex.Item
155 flex
154 flex
156 as={Text}
155 as={Text}
157− className={cn({
156+ className={cn('mono', {
158− 'mono black': isProxySet,
157+ black: !confirmed && isProxySet,
159− gray4: !isProxySet,
158+ gray4: !confirmed && !isProxySet,
159+ green3: confirmed,
160 })}>
160 })}>
161 {isProxySet ? proxyAddress : 'Unset'}
161 {isProxySet ? proxyAddress : 'Unset'}
162 </Flex.Item>
162 </Flex.Item>
163− {isProxySet && (
163+ {!confirmed && isProxySet && (
164 <Flex.Item as={LinkButton} onClick={unset} disabled={inputsLocked}>
164 <Flex.Item as={LinkButton} onClick={unset} disabled={inputsLocked}>
165 Unset
165 Unset
166 </Flex.Item>
166 </Flex.Item>
167 )}
167 )}
168 </Grid.Item>
168 </Grid.Item>
169
169
170+ {confirmed ? (
171+ <Grid.Item full className="mb4" />
172+ ) : (
170− <Grid.Item full as={Input} {...addressInput} className="mv4" />
173+ <Grid.Item full as={Input} {...addressInput} className="mv4" />
171−
174+ )}
175+
176+ <Grid.Item
177+ full
172− <Grid.Item full as={InlineEthereumTransaction} {...bind} />
178+ as={InlineEthereumTransaction}
179+ {...bind}
180+ onReturn={() => pop()}
181+ />
173 </Grid>
182 </Grid>
174 );
183 );
175}
184}
src/views/Admin/Reticket/ReticketExecute.js+1
@@ -93,6 +93,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
93 full
93 full
94 className="mt3"
94 className="mt3"
95 as={RestartButton}
95 as={RestartButton}
96+ solid
96 onClick={goToRestart}>
97 onClick={goToRestart}>
97 Restart
98 Restart
98 </Grid.Item>
99 </Grid.Item>
src/views/Invite/InviteEmail.js−1
@@ -256,7 +256,6 @@ export default function InviteEmail() {
256 gasLimit: GAS_LIMIT.toString(),
256 gasLimit: GAS_LIMIT.toString(),
257 nonce: nonce + i,
257 nonce: nonce + i,
258 chainId,
258 chainId,
259− setStx: () => {},
260 });
259 });
261 const rawTx = hexify(signedTx.serialize());
260 const rawTx = hexify(signedTx.serialize());
262
261
src/views/Point.js+1
@@ -19,6 +19,7 @@ import { useSyncOwnedPoints } from 'lib/useSyncPoints';
19import Actions from './Point/Actions';
19import Actions from './Point/Actions';
20import { useWallet } from 'store/wallet';
20import { useWallet } from 'store/wallet';
21import usePermissionsForPoint from 'lib/usePermissionsForPoint';
21import usePermissionsForPoint from 'lib/usePermissionsForPoint';
22+import MiniBackButton from 'components/MiniBackButton';
22
23
23export default function Point() {
24export default function Point() {
24 const { push, names } = useHistory();
25 const { push, names } = useHistory();