on master help sign in
feat: add tank usage to useEthereumTransaction and update CreateGalaxy
commitb87ca5c2d7bcfd0ce3f395c07ca435ed68ebbfa1 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateThu 18 Jul 2019, 12:11 (7 years ago) parent2b8b988
17 files changed+126−69
src/components/InlineEthereumTransaction.js+15−3 src/components/old/StatelessTransaction.js+1−2 src/indigo-react/lib/useForm.js+3−9 src/lib/error.js+4−2 src/lib/tank.js+7−13 src/lib/txn.js−3 src/lib/useDeepEqualReference.js+22 src/lib/useEthereumTransaction.js+41−4 src/lib/usePermissionsForPoint.js+5−8 src/lib/validators.js+15−14 src/lib/wallet.js+2−1 src/views/Activate/PassportTransfer.js+1−1 src/views/Admin/AdminEditPermissions.js+2−2 src/views/Admin/AdminSetProxy.js+3−2 src/views/Admin/Reticket/ReticketExecute.js+1−1 src/views/CreateGalaxy.js+2−1 src/views/Invite/InviteEmail.js+2−3
src/components/InlineEthereumTransaction.js+15−3
@@ -10,12 +10,14 @@ import {
1010 Text,
1111 HelpText,
1212} from 'indigo-react';
13+import { fromWei } from 'web3-utils';
1314
1415import { useCheckboxInput } from 'lib/useInputs';
15−import { hexify } from 'lib/txn';
1616import { useExploreTxUrl } from 'lib/explorer';
17+import { hexify } from 'lib/txn';
1718
1819import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
20+import WarningBox from './WarningBox';
1921
2022export default function InlineEthereumTransaction({
2123 // from useEthereumTransaction.bind
@@ -34,6 +36,7 @@ export default function InlineEthereumTransaction({
3436 txHash,
3537 nonce,
3638 chainId,
39+ needFunds,
3740 signedTransaction,
3841
3942 // additional from parent
@@ -96,8 +99,8 @@ export default function InlineEthereumTransaction({
9699 as={ForwardButton}
97100 solid
98101 success
99− disabled={broadcasted}
100− loading={broadcasted}
102+ disabled={broadcasted || needFunds}
103+ loading={broadcasted || needFunds}
101104 onClick={() => broadcast()}>
102105 Send Transaction
103106 </Grid.Item>
@@ -126,6 +129,15 @@ export default function InlineEthereumTransaction({
126129 </Grid.Item>
127130 )}
128131
132+ {needFunds && (
133+ <Grid.Item full as={WarningBox} className="mt3">
134+ The address {needFunds.address} needs at least{' '}
135+ {fromWei(needFunds.minBalance)} ETH and currently has{' '}
136+ {fromWei(needFunds.balance)} ETH. Waiting until the account has enough
137+ funds.
138+ </Grid.Item>
139+ )}
140+
129141 {showConfigureInput && (
130142 <>
131143 <Grid.Item full as={ToggleInput} {...advancedInput} />
src/components/old/StatelessTransaction.js+1−2
@@ -15,8 +15,6 @@ import { compose } from '../../lib/lib';
1515import * as tank from '../../lib/tank';
1616import {
1717 sendSignedTransaction,
18− fromWei,
19− toWei,
2018 hexify,
2119 renderSignedTx,
2220 signTransaction,
@@ -26,6 +24,7 @@ import { withNetwork } from '../../store/network';
2624import { withHistory } from '../../store/history';
2725import { withWallet } from '../../store/wallet';
2826import { withTxnCursor } from '../../store/txnCursor';
27+import { fromWei, toWei } from 'web3-utils';
2928
3029const SUBMISSION_STATES = {
3130 PROMPT: 'Send transaction',
src/indigo-react/lib/useForm.js+3−9
@@ -1,10 +1,10 @@
11import { useCallback, useMemo } from 'react';
2−import { isEqual, keyBy, get, every, some } from 'lodash';
2+import { keyBy, get, every, some } from 'lodash';
33
44import { compose } from 'lib/lib';
55import { kDefaultValidator } from 'lib/validators';
66import useSetState from 'lib/useSetState';
7−import usePreviousValue from 'lib/usePreviousValue';
7+import useDeepEqualReference from 'lib/useDeepEqualReference';
88
99// interface InputConfig {
1010// name: string;
@@ -26,13 +26,7 @@ const defaultsFor = (configs, mapper) =>
2626 * useForm manages a set of inputs for rendering them in a loop
2727 */
2828export default function useForm(inputConfigs = []) {
29− // track the old value of the config set
30− const previousConfigs = usePreviousValue(inputConfigs);
31− // then compare equality
32− const configsAreEqual = isEqual(inputConfigs, previousConfigs);
33− // if equality changes, give `configs` a new identity
34− // eslint-disable-next-line react-hooks/exhaustive-deps
35− const configs = useMemo(() => inputConfigs, [configsAreEqual]);
29+ const configs = useDeepEqualReference(inputConfigs);
3630
3731 const byName = useMemo(() => keyBy(configs, 'name'), [configs]);
3832
src/lib/error.js+4−2
@@ -1,3 +1,5 @@
1+import { fromWei } from 'web3-utils';
2+
13const BRIDGE_ERROR = {
24 MISSING_WEB3: 'no web3 object found',
35 MISSING_WALLET: 'no wallet found',
@@ -16,13 +18,13 @@ const BRIDGE_ERROR = {
1618const renderTxnError = (web3, msg) => {
1719 const txnCost = (web3, msg) => {
1820 const costWei = msg.match(/upfront cost is: ([0-9]+)/);
19− const costEth = web3.utils.fromWei(costWei[1], 'ether');
21+ const costEth = fromWei(costWei[1], 'ether');
2022 return parseFloat(costEth).toFixed(4);
2123 };
2224
2325 const acctBalance = (web3, msg) => {
2426 const balWei = msg.match(/sender's account only has: ([0-9]+)/);
25− const balEth = web3.utils.fromWei(balWei[1], 'ether');
27+ const balEth = fromWei(balWei[1], 'ether');
2628 return parseFloat(balEth).toFixed(4);
2729 };
2830
src/lib/tank.js+7−13
@@ -95,24 +95,18 @@ const ensureFundsFor = async (
9595
9696// returns a promise that resolves when address has at least minBalance
9797function waitForBalance(web3, address, minBalance, askForFunding, gotFunding) {
98− let oldBalance = null;
9998 return retry(async (bail, n) => {
10099 const balance = await web3.eth.getBalance(address);
101100 if (balance >= minBalance) {
102− // if we ever asked for funding, retract that request now
103− if (gotFunding && oldBalance !== null) {
104− gotFunding();
105− }
101+ gotFunding && gotFunding();
106102 return;
107103 } else {
108− if (balance !== oldBalance) {
109− // TODO: minBalance is a `number` type
110− // but we want to display ETH, which will never accept a number
111− // but instead wants string or BN/BigNumber.
112− // this will be heavily refactored to correctly do BN math, but until
113− // then we'll manually convert this number to an integer string
114− askForFunding(address, minBalance.toFixed(), balance);
115− }
104+ // TODO: minBalance is a `number` type
105+ // but we want to display ETH, which will never accept a number
106+ // but instead wants string or BN/BigNumber.
107+ // this will be heavily refactored to correctly do BN math, but until
108+ // then we'll manually convert this number to an integer string
109+ askForFunding(address, minBalance.toFixed(), balance);
116110 throw new Error('retrying');
117111 }
118112 }, RETRY_OPTIONS);
src/lib/txn.js−3
@@ -194,8 +194,5 @@ export {
194194 getTxnInfo,
195195 hexify,
196196 renderSignedTx,
197− toHex,
198− toWei,
199− fromWei,
200197 canDecodePatp,
201198};
src/lib/useDeepEqualReference.jsnew file+22
@@ -0,0 +1,22 @@
1+import { useRef } from 'react';
2+import { isEqual } from 'lodash';
3+
4+import usePreviousValue from './usePreviousValue';
5+
6+// changes the referential identity of value on deep equality change
7+export default function useDeepEqualReference(value) {
8+ const ref = useRef(value);
9+
10+ // track the old value of the config set
11+ const prevValue = usePreviousValue(value);
12+
13+ // then compare equality
14+ const areEqual = isEqual(value, prevValue);
15+
16+ // if equality changes, give value a new identity
17+ if (!areEqual) {
18+ ref.current = value;
19+ }
20+
21+ return ref.current;
22+}
src/lib/useEthereumTransaction.js+41−4
@@ -1,17 +1,21 @@
11import { useCallback, useState } from 'react';
2−import { fromWei } from 'web3-utils';
2+import { Just } from 'folktale/maybe';
3+import { fromWei, toWei } from 'web3-utils';
34
45import { useNetwork } from 'store/network';
56import { useWallet } from 'store/wallet';
7+import { usePointCursor } from 'store/pointCursor';
68
9+import { GAS_LIMITS } from './constants';
710import {
811 signTransaction,
912 sendSignedTransaction,
1013 waitForTransactionConfirm,
14+ hexify,
1115} from './txn';
1216import * as need from './need';
13−import { GAS_LIMITS } from './constants';
1417import useLifecycle from './useLifecycle';
18+import { ensureFundsFor } from './tank';
1519
1620const STATE = {
1721 NONE: 'NONE',
@@ -30,6 +34,8 @@ export default function useEthereumTransaction(
3034) {
3135 const { wallet, walletType, walletHdPath } = useWallet();
3236 const { web3, networkType } = useNetwork();
37+ const { pointCursor } = usePointCursor();
38+
3339 const _web3 = need.web3(web3);
3440 const _wallet = need.wallet(wallet);
3541
@@ -52,6 +58,7 @@ export default function useEthereumTransaction(
5258 const [unsignedTransaction, setUnsignedTransaction] = useState();
5359 const [signedTransaction, setSignedTransaction] = useState();
5460 const [txHash, setTxHash] = useState();
61+ const [needFunds, setNeedFunds] = useState();
5562
5663 const initializing = nonce === undefined || chainId === undefined;
5764 const constructed = !!unsignedTransaction;
@@ -111,10 +118,30 @@ export default function useEthereumTransaction(
111118 const broadcast = useCallback(async () => {
112119 try {
113120 setError(undefined);
121+
122+ const rawTx = hexify(signedTransaction.serialize());
123+ const cost = toWei(gasPrice.toFixed(0), 'gwei') * gasLimit;
124+
125+ let usedTank = false;
126+ // if this ethereum transaction is being executed by a specific point
127+ // see if we can use the tank
128+ if (Just.hasInstance(pointCursor)) {
129+ usedTank = await ensureFundsFor(
130+ _web3,
131+ pointCursor.value,
132+ _wallet.address,
133+ cost,
134+ [rawTx],
135+ (address, minBalance, balance) =>
136+ setNeedFunds({ address, minBalance, balance }),
137+ () => setNeedFunds(undefined)
138+ );
139+ }
140+
114141 const txHash = await sendSignedTransaction(
115142 _web3,
116143 signedTransaction,
117− /* doubtNonceError */ false
144+ /* doubtNonceError */ usedTank
118145 );
119146
120147 setState(STATE.BROADCASTED);
@@ -126,7 +153,15 @@ export default function useEthereumTransaction(
126153 } catch (error) {
127154 setError(error);
128155 }
129− }, [_web3, setError, signedTransaction]);
156+ }, [
157+ _wallet.address,
158+ _web3,
159+ gasLimit,
160+ gasPrice,
161+ pointCursor,
162+ setError,
163+ signedTransaction,
164+ ]);
130165
131166 const reset = useCallback(() => {
132167 setUnsignedTransaction(undefined);
@@ -135,6 +170,7 @@ export default function useEthereumTransaction(
135170 setGasPrice(suggestedGasPrice);
136171 setState(STATE.NONE);
137172 setError(undefined);
173+ setNeedFunds(undefined);
138174 }, [suggestedGasPrice, setError]);
139175
140176 const resetGasPrice = useCallback(() => setGasPrice(suggestedGasPrice), [
@@ -186,6 +222,7 @@ export default function useEthereumTransaction(
186222 resetGasPrice,
187223 nonce,
188224 chainId,
225+ needFunds,
189226 };
190227
191228 return {
src/lib/usePermissionsForPoint.js+5−8
@@ -5,7 +5,7 @@ import { useNetwork } from 'store/network';
55
66import * as need from 'lib/need';
77
8−import { eqAddr, ETH_ZERO_ADDR } from './wallet';
8+import { eqAddr, isZeroAddress } from './wallet';
99
1010const NULL_PERMISSIONS = {
1111 isPlanet: false,
@@ -55,13 +55,10 @@ export default function usePermissionsForPoint(address, point) {
5555 const isVotingProxy = eqAddr(address, details.votingProxy);
5656 const isTransferProxy = eqAddr(address, details.transferProxy);
5757
58− const isManagementProxySet = !eqAddr(
59− ETH_ZERO_ADDR,
60− details.managementProxy
61− );
62− const isSpawnProxySet = !eqAddr(ETH_ZERO_ADDR, details.spawnProxy);
63− const isVotingProxySet = !eqAddr(ETH_ZERO_ADDR, details.votingProxy);
64− const isTransferProxySet = !eqAddr(ETH_ZERO_ADDR, details.transferProxy);
58+ const isManagementProxySet = !isZeroAddress(details.managementProxy);
59+ const isSpawnProxySet = !isZeroAddress(details.spawnProxy);
60+ const isVotingProxySet = !isZeroAddress(details.votingProxy);
61+ const isTransferProxySet = !isZeroAddress(details.transferProxy);
6562
6663 const canManage = isOwner || isManagementProxy;
6764 const canTransfer = isOwner || isTransferProxy;
src/lib/validators.js+15−14
@@ -5,6 +5,7 @@ import { identity, includes } from 'lodash';
55import { isValidAddress, ETH_ZERO_ADDR, ETH_ZERO_ADDR_SHORT } from './wallet';
66import patp2dec from './patp2dec';
77import { patpStringLength } from './lib';
8+import { MIN_GALAXY, MAX_GALAXY } from './constants';
89
910// NOTE: do not use the /g modifier for these regexes
1011// https://stackoverflow.com/a/21373261
@@ -90,12 +91,12 @@ export const validateGalaxy = m =>
9091 let point;
9192 try {
9293 point = patp2dec(d);
93− return point >= 0 && point < 256;
94+ return point >= MIN_GALAXY && point <= MAX_GALAXY;
9495 } catch (e) {
9596 return false;
9697 }
9798 },
98− error: 'This is not a valid galaxy',
99+ error: 'This is not a valid galaxy.',
99100 });
100101
101102export const validatePoint = m =>
@@ -108,7 +109,7 @@ export const validatePoint = m =>
108109 return false;
109110 }
110111 },
111− error: 'This is not a valid point',
112+ error: 'This is not a valid point.',
112113 });
113114
114115export const validateTicket = m =>
@@ -121,7 +122,7 @@ export const validateTicket = m =>
121122 return false;
122123 }
123124 },
124− error: 'This is not a valid ticket',
125+ error: 'This is not a valid ticket.',
125126 });
126127
127128export const validateShard = m =>
@@ -134,7 +135,7 @@ export const validateShard = m =>
134135 return false;
135136 }
136137 },
137− error: 'This is not a valid shard',
138+ error: 'This is not a valid shard.',
138139 });
139140
140141export const validateOneOf = (options = []) => m =>
@@ -148,7 +149,7 @@ export const validateHexString = m =>
148149 simpleValidatorWrapper({
149150 prevMessage: m,
150151 validator: d => isHexString.test(d),
151− error: 'This is not a valid hex string',
152+ error: 'This is not a valid hex string.',
152153 });
153154
154155// @deprecate
@@ -156,7 +157,7 @@ export const validateNetworkKey = m =>
156157 simpleValidatorWrapper({
157158 prevMessage: m,
158159 validator: d => plain64CharHexValue.test(d),
159− error: 'This is not a valid network key',
160+ error: 'This is not a valid network key.',
160161 });
161162
162163// @deprecate
@@ -164,7 +165,7 @@ export const validateNetworkSeed = m =>
164165 simpleValidatorWrapper({
165166 prevMessage: m,
166167 validator: d => plain64CharHexValue.test(d),
167− error: 'This is not a valid network seed',
168+ error: 'This is not a valid network seed.',
168169 });
169170
170171// Checks if a string is a valid ethereum address
@@ -172,14 +173,14 @@ export const validateEthereumAddress = m =>
172173 simpleValidatorWrapper({
173174 prevMessage: m,
174175 validator: d => isValidAddress(d),
175− error: 'This is not a valid Ethereum address',
176+ error: 'This is not a valid Ethereum address.',
176177 });
177178
178179export const validateEmail = m =>
179180 simpleValidatorWrapper({
180181 prevMessage: m,
181182 validator: d => emailRegExp.test(d),
182− error: 'This is not a valid email address',
183+ error: 'This is not a valid email address.',
183184 });
184185
185186export const validateInt = m =>
@@ -194,7 +195,7 @@ export const validateInt = m =>
194195 return false;
195196 }
196197 },
197− error: 'This is not a valid number',
198+ error: 'This is not a valid number.',
198199 });
199200
200201export const validateExactly = (value, error) => m =>
@@ -208,7 +209,7 @@ export const validateNotAny = (values = []) => m =>
208209 simpleValidatorWrapper({
209210 prevMessage: m,
210211 validator: d => !values.includes(d),
211− error: `Cannot be ${m.data}`,
212+ error: `Cannot be ${m.data}.`,
212213 });
213214
214215export const validateLength = l => m =>
@@ -221,7 +222,7 @@ export const validateLength = l => m =>
221222 return false;
222223 }
223224 },
224− error: `Must be exactly ${l} characters`,
225+ error: `Must be exactly ${l} characters.`,
225226 });
226227
227228export const validateMaximumLength = l => m =>
@@ -241,7 +242,7 @@ export const validateGreaterThan = l => m =>
241242 simpleValidatorWrapper({
242243 prevMessage: m,
243244 validator: d => d > l,
244− error: `Must be at least ${l}`,
245+ error: `Must be at least ${l}.`,
245246 });
246247
247248export const validatePatpByteLength = byteLength => {
src/lib/wallet.js+2−1
@@ -54,7 +54,8 @@ export const keccak256 = str =>
5454
5555export const isValidAddress = a => '0x0' === a || isAddress(a);
5656
57−export const isZeroAddress = a => ETH_ZERO_ADDR === a;
57+export const isZeroAddress = a =>
58+ a === ETH_ZERO_ADDR || a === ETH_ZERO_ADDR_SHORT;
5859
5960export const toChecksumAddress = address => {
6061 const addr = stripHexPrefix(address).toLowerCase();
src/views/Activate/PassportTransfer.js+1−1
@@ -1,5 +1,6 @@
11import React, { useState, useCallback } from 'react';
22import { Grid, Text, ErrorText } from 'indigo-react';
3+import { fromWei } from 'web3-utils';
34
45import { useNetwork } from 'store/network';
56import { useWallet } from 'store/wallet';
@@ -13,7 +14,6 @@ import {
1314 TRANSACTION_PROGRESS,
1415} from 'lib/reticket';
1516import timeout from 'lib/timeout';
16−import { fromWei } from 'lib/txn';
1717
1818import WarningBox from 'components/WarningBox';
1919import LoadingBar from 'components/LoadingBar';
src/views/Admin/AdminEditPermissions.js+2−2
@@ -8,7 +8,7 @@ import { usePointCursor } from 'store/pointCursor';
88import { usePointCache } from 'store/pointCache';
99
1010import * as need from 'lib/need';
11−import { ETH_ZERO_ADDR } from 'lib/wallet';
11+import { isZeroAddress } from 'lib/wallet';
1212import { PROXY_TYPE, proxyTypeToHuman } from 'lib/proxy';
1313import { useLocalRouter } from 'lib/LocalRouter';
1414import capitalize from 'lib/capitalize';
@@ -48,7 +48,7 @@ export default function AdminEditPermissions() {
4848 ]);
4949
5050 const renderProxyAction = (proxyType, address) => {
51− if (eqAddr(address, ETH_ZERO_ADDR)) {
51+ if (isZeroAddress(address)) {
5252 address = 'Not set';
5353 } else if (eqAddr(address, _contracts.delegatedSending.address)) {
5454 address = `${address} (invites contract)`;
src/views/Admin/AdminSetProxy.js+3−2
@@ -14,7 +14,7 @@ import {
1414} from 'lib/proxy';
1515import * as need from 'lib/need';
1616import { useLocalRouter } from 'lib/LocalRouter';
17−import { ETH_ZERO_ADDR, eqAddr } from 'lib/wallet';
17+import { ETH_ZERO_ADDR, eqAddr, isZeroAddress } from 'lib/wallet';
1818import capitalize from 'lib/capitalize';
1919import { useAddressInput, useCheckboxInput } from 'lib/useInputs';
2020import useEthereumTransaction from 'lib/useEthereumTransaction';
@@ -133,6 +133,7 @@ export default function AdminSetProxy() {
133133 label: 'Unset',
134134 inverseLabel: 'Specify',
135135 initialValue: false,
136+ disabled: inputsLocked,
136137 });
137138 const [
138139 addressInput,
@@ -164,7 +165,7 @@ export default function AdminSetProxy() {
164165 ]);
165166
166167 const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
167− const isProxySet = !eqAddr(ETH_ZERO_ADDR, proxyAddress);
168+ const isProxySet = !isZeroAddress(proxyAddress);
168169
169170 const proxyAddressLabel = `${
170171 confirmed ? 'New' : 'Current'
src/views/Admin/Reticket/ReticketExecute.js+1−1
@@ -1,12 +1,12 @@
11import React, { useState, useCallback } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
33import { Grid, H4, Text, ErrorText } from 'indigo-react';
4+import { fromWei } from 'web3-utils';
45
56import {
67 reticketPointBetweenWallets,
78 TRANSACTION_PROGRESS,
89} from 'lib/reticket';
9−import { fromWei } from 'lib/txn';
1010import * as need from 'lib/need';
1111import useLifecycle from 'lib/useLifecycle';
1212
src/views/CreateGalaxy.js+2−1
@@ -86,7 +86,7 @@ export default function CreateGalaxy() {
8686 error ||
8787 isAvailable.matchWith({
8888 Nothing: () => 'Loading availabiliy...', // TODO: make async loading?
89− Just: p => (p.value ? undefined : 'This galaxy is already owned'),
89+ Just: p => (p.value ? undefined : 'This galaxy is already owned.'),
9090 }),
9191 });
9292
@@ -109,6 +109,7 @@ export default function CreateGalaxy() {
109109 return;
110110 }
111111
112+ setError();
112113 setIsAvailable(Nothing());
113114
114115 let cancelled = false;
src/views/Invite/InviteEmail.js+2−3
@@ -13,6 +13,7 @@ import {
1313 useForm,
1414} from 'indigo-react';
1515import { uniq } from 'lodash';
16+import { fromWei, toWei } from 'web3-utils';
1617
1718import { usePointCursor } from 'store/pointCursor';
1819import { useNetwork } from 'store/network';
@@ -25,8 +26,6 @@ import {
2526 sendSignedTransaction,
2627 waitForTransactionConfirm,
2728 hexify,
28− fromWei,
29− toWei,
3029} from 'lib/txn';
3130import * as tank from 'lib/tank';
3231import { useLocalRouter } from 'lib/LocalRouter';
@@ -309,7 +308,7 @@ export default function InviteEmail() {
309308 inputs.map(input => invites[input.name].rawTx),
310309 (address, minBalance, balance) =>
311310 setNeedFunds({ address, minBalance, balance }),
312− () => setNeedFunds(false)
311+ () => setNeedFunds(undefined)
313312 );
314313
315314 setStatus(STATUS.SENDING);