on master help sign in
Merge branch 'mino' into fix/tank-messaging
ab7e847Matt Condon (shrugs)
src/components/InlineEthereumTransaction.js+1−1
@@ -140,7 +140,7 @@ export default function InlineEthereumTransaction({
140 return (
140 return (
141 <Grid className={cn(className, 'mt1')}>
141 <Grid className={cn(className, 'mt1')}>
142 <BridgeForm validate={validate} onValues={onValues}>
142 <BridgeForm validate={validate} onValues={onValues}>
143− {({ handleSubmit }) => (
143+ {() => (
144 <>
144 <>
145 {renderPrimarySection()}
145 {renderPrimarySection()}
146
146
src/form/Inputs.js+18−2
@@ -7,6 +7,8 @@ import {
7 buildFormatter,
7 buildFormatter,
8 downcase,
8 downcase,
9 ensurePatFormat,
9 ensurePatFormat,
10+ stripHexPrefix,
11+ ensureHexPrefix,
10} from 'form/formatters';
12} from 'form/formatters';
11import { DEFAULT_HD_PATH } from 'lib/wallet';
13import { DEFAULT_HD_PATH } from 'lib/wallet';
12import InputSigil from 'components/InputSigil';
14import InputSigil from 'components/InputSigil';
@@ -17,7 +19,9 @@ const PLACEHOLDER_MNEMONIC =
17 'example crew supreme gesture quantum web media hazard theory mercy wing kitten';
19 'example crew supreme gesture quantum web media hazard theory mercy wing kitten';
18const PLACEHOLDER_TICKET = '~sampel-ticlyt-migfun-falmel';
20const PLACEHOLDER_TICKET = '~sampel-ticlyt-migfun-falmel';
19const PLACEHOLDER_ADDRESS = '0x';
21const PLACEHOLDER_ADDRESS = '0x';
22+const PLACEHOLDER_HEX = '0x';
20−const PLACEHOLDER_PRIVATE_KEY = '0x';
23+const PLACEHOLDER_PRIVATE_KEY =
24+ 'a44de2416ee6beb2f323fab48b432925c9785808d33a6ca6d7ba00b45e9370c3';
21const PLACEHOLDER_EMAIL = 'email@example.com';
25const PLACEHOLDER_EMAIL = 'email@example.com';
22
26
23const formatPat = buildFormatter([downcase, ensurePatFormat]);
27const formatPat = buildFormatter([downcase, ensurePatFormat]);
@@ -136,11 +140,23 @@ export function HexInput({ ...rest }) {
136 return (
140 return (
137 <Input
141 <Input
138 type="text"
142 type="text"
139− placeholder={PLACEHOLDER_PRIVATE_KEY}
143+ placeholder={PLACEHOLDER_HEX}
140 autoCapitalize="none"
144 autoCapitalize="none"
141 autoComplete="off"
145 autoComplete="off"
142 autoCorrect="off"
146 autoCorrect="off"
143 mono
147 mono
148+ config={{ parse: ensureHexPrefix }}
149+ {...rest}
150+ />
151+ );
152+}
153+
154+export function PrivateKeyInput({ ...rest }) {
155+ return (
156+ <HexInput
157+ type="password"
158+ placeholder={PLACEHOLDER_PRIVATE_KEY}
159+ config={{ parse: stripHexPrefix }}
144 {...rest}
160 {...rest}
145 />
161 />
146 );
162 );
src/form/ValuesHandler.js+3−2
@@ -6,12 +6,13 @@ import { useForm } from 'react-final-form';
6 */
6 */
7export default function ValuesHandler({ valid, validating, values, onValues }) {
7export default function ValuesHandler({ valid, validating, values, onValues }) {
8 const form = useForm();
8 const form = useForm();
9+ const validationPaused = form.isValidationPaused();
9
10
10 useEffect(() => {
11 useEffect(() => {
11− if (!validating && !form.isValidationPaused()) {
12+ if (!validating && !validationPaused) {
12 onValues && onValues({ valid, values, form });
13 onValues && onValues({ valid, values, form });
13 }
14 }
14− }, [form, onValues, valid, validating, values]);
15+ }, [form, onValues, valid, validating, validationPaused, values]);
15
16
16 return null;
17 return null;
17}
18}
src/form/formatters.js+18−7
@@ -1,5 +1,6 @@
1import { compose, strSplice } from 'lib/lib';
1import { compose, strSplice } from 'lib/lib';
2
2
3+const HEX_PREFIX = '0x';
3const TICKET_MAX_BYTE_LEN = 32; // tickets can be as large as 32 bytes
4const TICKET_MAX_BYTE_LEN = 32; // tickets can be as large as 32 bytes
4const PAT_BLOCK_CHAR_LENGTH = 6; // pat{p,q} blocks are 6 characters long
5const PAT_BLOCK_CHAR_LENGTH = 6; // pat{p,q} blocks are 6 characters long
5
6
@@ -38,6 +39,14 @@ export const ensurePatFormat = s => {
38 return s;
39 return s;
39};
40};
40
41
42+export const ensureHexPrefix = s => {
43+ if (!s || s.length < HEX_PREFIX.length) {
44+ return s || '';
45+ }
46+
47+ return s.indexOf(HEX_PREFIX) !== 0 ? `${HEX_PREFIX}${s}` : s;
48+};
49+
41export const convertToNumber = s => {
50export const convertToNumber = s => {
42 try {
51 try {
43 return parseInt(s, 10);
52 return parseInt(s, 10);
@@ -54,10 +63,12 @@ export const downcase = s => {
54 return s.toLowerCase();
63 return s.toLowerCase();
55};
64};
56
65
57−// const hideAllButLast = s => {
66+export const stripHexPrefix = s => {
58−// const ll = s[s.length -1];
67+ if (!s) {
59−// const bs = fill(s.length - 1, '•')
68+ return s;
60−// return `${bs}${ll}`
69+ }
61−// };
70+
62−//
71+ return s.toLowerCase().indexOf(HEX_PREFIX) === 0
63−// const hideAll = s => fill(s.length, '•');
72+ ? s.slice(HEX_PREFIX.length)
73+ : s;
74+};
src/form/validators.js+14−1
@@ -13,9 +13,14 @@ import {
13 validateGreaterThan,
13 validateGreaterThan,
14 validateEmail,
14 validateEmail,
15 validateNotNullAddress,
15 validateNotNullAddress,
16+ validateHexPrefix,
17+ validateLength,
16} from 'lib/validators';
18} from 'lib/validators';
17import isPromise from 'lib/isPromise';
19import isPromise from 'lib/isPromise';
18
20
21+const PRIVATE_KEY_CHAR_LENGTH = 64; // 64 hex characters
22+const ADDRESS_CHAR_LENGTH = 40; // 40 hex characters
23+
19// iterate over validators, exiting early if there's an error
24// iterate over validators, exiting early if there's an error
20const buildValidator = (validators = [], validate) => value => {
25const buildValidator = (validators = [], validate) => value => {
21 for (const validator of validators) {
26 for (const validator of validators) {
@@ -75,15 +80,23 @@ export const buildSelectValidator = options =>
75export const buildHexValidator = length =>
80export const buildHexValidator = length =>
76 buildValidator([
81 buildValidator([
77 validateNotEmpty,
82 validateNotEmpty,
83+ validateHexPrefix,
78 validateHexString,
84 validateHexString,
79 validateHexLength(length),
85 validateHexLength(length),
80 ]);
86 ]);
87+export const buildPrivateKeyValidator = () =>
88+ buildValidator([
89+ validateNotEmpty,
90+ validateHexString,
91+ validateLength(PRIVATE_KEY_CHAR_LENGTH),
92+ ]);
81export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
93export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
82export const buildAddressValidator = () =>
94export const buildAddressValidator = () =>
83 buildValidator([
95 buildValidator([
84 validateNotEmpty,
96 validateNotEmpty,
97+ validateHexPrefix,
85 validateHexString,
98 validateHexString,
86− validateHexLength(40),
99+ validateHexLength(ADDRESS_CHAR_LENGTH),
87 validateNotNullAddress,
100 validateNotNullAddress,
88 validateEthereumAddress,
101 validateEthereumAddress,
89 ]);
102 ]);
src/lib/reticket.js+11−10
@@ -12,7 +12,7 @@ import { addHexPrefix } from './wallet';
12import { sendAndAwaitAll } from './txn';
12import { sendAndAwaitAll } from './txn';
13import getSuggestedGasPrice from './getSuggestedGasPrice';
13import getSuggestedGasPrice from './getSuggestedGasPrice';
14import { GAS_LIMITS } from './constants';
14import { GAS_LIMITS } from './constants';
15−import { toWei } from 'web3-utils';
15+import { toWei, toBN } from 'web3-utils';
16
16
17// the initial network key revision is always 1
17// the initial network key revision is always 1
18const INITIAL_NETWORK_KEY_REVISION = 1;
18const INITIAL_NETWORK_KEY_REVISION = 1;
@@ -143,14 +143,15 @@ export async function reticketPointBetweenWallets({
143 progress(TRANSACTION_PROGRESS.SIGNING);
143 progress(TRANSACTION_PROGRESS.SIGNING);
144
144
145 const suggestedGasPrice = await getSuggestedGasPrice(networkType);
145 const suggestedGasPrice = await getSuggestedGasPrice(networkType);
146− const gasPrice = parseInt(toWei(suggestedGasPrice.toFixed(), 'gwei'), 10);
146+ const gasPrice = toWei(suggestedGasPrice.toFixed(), 'gwei');
147+ const gasPriceBN = toBN(gasPrice);
147− let totalCost = 0;
148+ let totalCost = toBN(0);
148 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
149 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
149 txs = txs.map(tx => {
150 txs = txs.map(tx => {
150 tx.from = fromWallet.address;
151 tx.from = fromWallet.address;
151 tx.nonce = inviteNonce++;
152 tx.nonce = inviteNonce++;
152− tx.gasPrice = gasPrice;
153+ tx.gasPrice = gasPriceBN;
153− totalCost = totalCost + tx.gasPrice * tx.gas;
154+ totalCost = totalCost.add(gasPriceBN.mul(toBN(tx.gas)));
154 return tx;
155 return tx;
155 });
156 });
156
157
@@ -194,15 +195,15 @@ export async function reticketPointBetweenWallets({
194 progress(TRANSACTION_PROGRESS.CLEANING);
195 progress(TRANSACTION_PROGRESS.CLEANING);
195
196
196 // if non-trivial eth left in invite wallet, transfer to new ownership
197 // if non-trivial eth left in invite wallet, transfer to new ownership
197− let balance = await web3.eth.getBalance(fromWallet.address);
198+ let balance = toBN(await web3.eth.getBalance(fromWallet.address));
198 const gasLimit = GAS_LIMITS.SEND_ETH;
199 const gasLimit = GAS_LIMITS.SEND_ETH;
199− const sendEthCost = gasPrice * gasLimit;
200+ const sendEthCost = gasPriceBN.mul(toBN(gasLimit));
200− if (transferEth && balance > sendEthCost) {
201+ if (transferEth && balance.gt(sendEthCost)) {
201− const value = balance - sendEthCost;
202+ const value = balance.sub(sendEthCost);
202 const tx = {
203 const tx = {
203 to: toWallet.ownership.keys.address,
204 to: toWallet.ownership.keys.address,
204 value: value,
205 value: value,
205− gasPrice: gasPrice,
206+ gasPrice: gasPriceBN,
206 gas: gasLimit,
207 gas: gasLimit,
207 nonce: inviteNonce++,
208 nonce: inviteNonce++,
208 };
209 };
src/lib/tank.js+20−20
@@ -1,6 +1,7 @@
1// /lib/tank: functions for funding transactions
1// /lib/tank: functions for funding transactions
2
2
3import retry from 'async-retry';
3import retry from 'async-retry';
4+import { toBN } from 'web3-utils';
4import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
5import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
5
6
6//NOTE if accessing this in a localhost configuration fails with "CORS request
7//NOTE if accessing this in a localhost configuration fails with "CORS request
@@ -53,24 +54,29 @@ const ensureFundsFor = async (
53 askForFunding,
54 askForFunding,
54 gotFunding
55 gotFunding
55) => {
56) => {
56− let balance = await web3.eth.getBalance(address);
57+ const balance = toBN(await web3.eth.getBalance(address));
58+ cost = toBN(cost);
57
59
58− if (cost <= balance) {
60+ if (balance.gte(cost)) {
61+ console.log(
59− console.log('tank: already have sufficient funds', cost, address, balance);
62+ `tank: already have sufficient funds: ${address} proposed ` +
63+ `a transacton(s) that will cost ${cost.toString()}wei but it ` +
64+ `already has ${balance.toString}wei`
65+ );
60 return false;
66 return false;
61 }
67 }
62
68
63 try {
69 try {
70+ // TODO: if we can't always (easily) provide a point, and the
71+ // fundTransactions call is gonna fail anyway, should we maybe
72+ // just not bother doing this check in the first place?
64 if (point !== null) {
73 if (point !== null) {
65 const fundsRemaining = await remainingTransactions(point);
74 const fundsRemaining = await remainingTransactions(point);
66 if (fundsRemaining < signedTxs.length) {
75 if (fundsRemaining < signedTxs.length) {
67 throw new Error('tank: request invalid');
76 throw new Error('tank: request invalid');
68 }
77 }
69 } else {
78 } else {
70− console.log('tank: no point provided. skipping remaining-funds check');
79+ console.log('tank: no point provided. skipping remaining-funds check...');
71− //TODO if we can't always (easily) provide a point, and the
72− // fundTransactions call is gonna fail anyway, should we maybe
73− // just not bother doing this check in the first place?
74 }
80 }
75
81
76 const res = await fundTransactions(signedTxs);
82 const res = await fundTransactions(signedTxs);
@@ -79,16 +85,15 @@ const ensureFundsFor = async (
79 }
85 }
80
86
81 await waitForTransactionConfirm(web3, res.txHash);
87 await waitForTransactionConfirm(web3, res.txHash);
88+
82 const newBalance = await web3.eth.getBalance(address);
89 const newBalance = await web3.eth.getBalance(address);
83 console.log(
90 console.log(
84− 'tank: funds have confirmed',
91+ `tank: funds have confirmed: ${address} now has ` +
85− balance >= cost,
92+ ` ${newBalance.toString()}wei, up from ${balance.toString()}wei`
86− balance,
87− newBalance
88 );
93 );
89 return true;
94 return true;
90 } catch (e) {
95 } catch (e) {
91− console.log('tank: funding failed, waiting on user funds.', e);
96+ console.log('tank: funding failed, waiting on user funds...', e);
92 await waitForBalance(web3, address, cost, askForFunding, gotFunding);
97 await waitForBalance(web3, address, cost, askForFunding, gotFunding);
93 }
98 }
94
99
@@ -98,17 +103,12 @@ const ensureFundsFor = async (
98// returns a promise that resolves when address has at least minBalance
103// returns a promise that resolves when address has at least minBalance
99function waitForBalance(web3, address, minBalance, askForFunding, gotFunding) {
104function waitForBalance(web3, address, minBalance, askForFunding, gotFunding) {
100 return retry(async (bail, n) => {
105 return retry(async (bail, n) => {
101− const balance = await web3.eth.getBalance(address);
106+ const balance = toBN(await web3.eth.getBalance(address));
102− if (balance >= minBalance) {
107+ if (balance.gte(minBalance)) {
103 gotFunding && gotFunding();
108 gotFunding && gotFunding();
104 return;
109 return;
105 } else {
110 } else {
106− // TODO: minBalance is a `number` type
107− // but we want to display ETH, which will never accept a number
108− // but instead wants string or BN/BigNumber.
109− // this will be heavily refactored to correctly do BN math, but until
110− // then we'll manually convert this number to an integer string
111− askForFunding(address, minBalance.toFixed(), balance);
111+ askForFunding(address, minBalance.toString(), balance);
112 throw new Error('retrying');
112 throw new Error('retrying');
113 }
113 }
114 }, RETRY_OPTIONS);
114 }, RETRY_OPTIONS);
src/lib/useEthereumTransaction.js+3−2
@@ -1,6 +1,6 @@
1import { useCallback, useEffect, useState } from 'react';
1import { useCallback, useEffect, useState } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3−import { toWei } from 'web3-utils';
3+import { toWei, toBN } from 'web3-utils';
4
4
5import { useNetwork } from 'store/network';
5import { useNetwork } from 'store/network';
6import { useWallet } from 'store/wallet';
6import { useWallet } from 'store/wallet';
@@ -136,7 +136,8 @@ export default function useEthereumTransaction(
136 setError(undefined);
136 setError(undefined);
137
137
138 const rawTx = hexify(signedTransaction.serialize());
138 const rawTx = hexify(signedTransaction.serialize());
139− const cost = toWei(gasPrice.toFixed(0), 'gwei') * gasLimit;
139+ const costGwei = toBN(gasLimit).mul(toBN(gasPrice));
140+ const cost = toWei(costGwei.toString(), 'gwei');
140
141
141 let usedTank = false;
142 let usedTank = false;
142 // if this ethereum transaction is being executed by a specific point
143 // if this ethereum transaction is being executed by a specific point
src/lib/validators.js+8−2
@@ -14,8 +14,11 @@ import { MIN_GALAXY, MAX_GALAXY } from './constants';
14// via: https://emailregex.com/
14// via: https://emailregex.com/
15const emailRegExp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
15const emailRegExp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
16
16
17−// is this only hex values with a 0x prefix?
17+// validate only strings that use hex characters
18−const isHexString = /0x[0-9A-Fa-f]/;
18+const isHexString = /[0-9A-Fa-f]/;
19+
20+// validate only strings that are prefixed with 0x
21+const isHexPrefixed = /^0x/;
19
22
20// Validates a bip39 mnemonic
23// Validates a bip39 mnemonic
21export const validateMnemonic = v =>
24export const validateMnemonic = v =>
@@ -64,6 +67,9 @@ export const validateOneOf = (options = []) => v =>
64export const validateHexString = v =>
67export const validateHexString = v =>
65 !isHexString.test(v) && 'This is not a valid hex string.';
68 !isHexString.test(v) && 'This is not a valid hex string.';
66
69
70+export const validateHexPrefix = v =>
71+ !isHexPrefixed.test(v) && 'Must include 0x prefix.';
72+
67export const validateEthereumAddress = v =>
73export const validateEthereumAddress = v =>
68 !isValidAddress(v) && 'This is not a valid Ethereum address.';
74 !isValidAddress(v) && 'This is not a valid Ethereum address.';
69
75
src/store/lib/useRekeyDateStore.js+17−6
@@ -44,18 +44,29 @@ export default function useRekeyDateStore() {
44 },
44 },
45 });
45 });
46
46
47+ // NB(shrugs) using try-catch here to simplify our error handling
48+ try {
47− if (logs.length === 0) {
49+ if (logs.length === 0) {
48− //TODO: better encoding for "no rekeyDate" state?
49− addToRekeyDateCache({
50− [point]: Just(Result.Error('No rekey date available.')),
50+ throw new Error('No logs available.');
51− });
51+ }
52− } else {
52+
53 // last log is most recent
53 // last log is most recent
54 const blockNumber = logs[logs.length - 1].blockNumber;
54 const blockNumber = logs[logs.length - 1].blockNumber;
55+ if (blockNumber === null) {
56+ // the "latest" parameter includes pending blocks,
57+ // which won't have a blockNumber just yet
58+ throw new Error('Latest rekey is still pending');
59+ }
60+
55 const block = await _web3.eth.getBlock(blockNumber);
61 const block = await _web3.eth.getBlock(blockNumber);
56 addToRekeyDateCache({
62 addToRekeyDateCache({
57 [point]: Just(Result.Ok(new Date(block.timestamp * 1000))),
63 [point]: Just(Result.Ok(new Date(block.timestamp * 1000))),
58 });
64 });
65+ } catch {
66+ //TODO: better encoding for "no rekeyDate" state?
67+ addToRekeyDateCache({
68+ [point]: Just(Result.Error('No rekey date available.')),
69+ });
59 }
70 }
60 },
71 },
61 [web3, contracts, addToRekeyDateCache]
72 [web3, contracts, addToRekeyDateCache]
src/views/Admin/AdminNetworkingKeys.js+1−1
@@ -332,7 +332,7 @@ export default function AdminNetworkingKeys() {
332 validate={validate}
332 validate={validate}
333 onValues={onValues}
333 onValues={onValues}
334 initialValues={initialValues}>
334 initialValues={initialValues}>
335− {({ handleSubmit }) => (
335+ {() => (
336 <>
336 <>
337 <Grid.Item
337 <Grid.Item
338 full
338 full
src/views/Invite/InviteEmail.js+21−2
@@ -18,7 +18,7 @@ import {
18 AccessoryIcon,
18 AccessoryIcon,
19} from 'indigo-react';
19} from 'indigo-react';
20import { uniq } from 'lodash';
20import { uniq } from 'lodash';
21−import { fromWei, toWei } from 'web3-utils';
21+import { fromWei, toWei, toBN } from 'web3-utils';
22import { FieldArray } from 'react-final-form-arrays';
22import { FieldArray } from 'react-final-form-arrays';
23
23
24import { usePointCursor } from 'store/pointCursor';
24import { usePointCursor } from 'store/pointCursor';
@@ -135,6 +135,7 @@ export default function InviteEmail() {
135 const canInput = status === STATUS.INPUT;
135 const canInput = status === STATUS.INPUT;
136 const isGenerating = status === STATUS.GENERATING;
136 const isGenerating = status === STATUS.GENERATING;
137 const canSend = status === STATUS.CAN_SEND;
137 const canSend = status === STATUS.CAN_SEND;
138+ const isFunding = status === STATUS.FUNDING;
138 const isSending = status === STATUS.SENDING;
139 const isSending = status === STATUS.SENDING;
139 const isFailed = status === STATUS.FAILURE;
140 const isFailed = status === STATUS.FAILURE;
140 const isDone = status === STATUS.SUCCESS;
141 const isDone = status === STATUS.SUCCESS;
@@ -306,11 +307,14 @@ export default function InviteEmail() {
306 }
307 }
307
308
308 setStatus(STATUS.FUNDING);
309 setStatus(STATUS.FUNDING);
310+ const totalCost = toBN(GAS_LIMIT)
311+ .mul(toBN(gasPrice))
312+ .mul(toBN(emails.length));
309 const tankWasUsed = await tank.ensureFundsFor(
313 const tankWasUsed = await tank.ensureFundsFor(
310 _web3,
314 _web3,
311 point,
315 point,
312 _wallet.address,
316 _wallet.address,
313− toWei((gasPrice * GAS_LIMIT * emails.length).toFixed(), 'gwei'),
317+ toWei(totalCost.toString(), 'gwei'),
314 names.map(name => invites[name].rawTx),
318 names.map(name => invites[name].rawTx),
315 (address, minBalance, balance) =>
319 (address, minBalance, balance) =>
316 setNeedFunds({ address, minBalance, balance }),
320 setNeedFunds({ address, minBalance, balance }),
@@ -473,6 +477,21 @@ export default function InviteEmail() {
473 );
477 );
474 }
478 }
475
479
480+ if (isFunding) {
481+ return (
482+ <Grid.Item
483+ full
484+ as={ForwardButton}
485+ className="mt4"
486+ solid
487+ success
488+ disabled
489+ accessory={`${visualProgress}/${fields.length}`}>
490+ {buttonText(status, fields.length)}
491+ </Grid.Item>
492+ );
493+ }
494+
476 if (isSending) {
495 if (isSending) {
477 return (
496 return (
478 <Grid.Item
497 <Grid.Item
src/views/Login/PrivateKey.js+4−4
@@ -9,8 +9,8 @@ import useLoginView from 'lib/useLoginView';
9
9
10import FormError from 'form/FormError';
10import FormError from 'form/FormError';
11import BridgeForm from 'form/BridgeForm';
11import BridgeForm from 'form/BridgeForm';
12−import { HexInput } from 'form/Inputs';
12+import { PrivateKeyInput } from 'form/Inputs';
13−import { buildHexValidator, composeValidator } from 'form/validators';
13+import { composeValidator, buildPrivateKeyValidator } from 'form/validators';
14
14
15import SubmitButton from 'form/SubmitButton';
15import SubmitButton from 'form/SubmitButton';
16
16
@@ -22,7 +22,7 @@ export default function PrivateKey({ className, goHome }) {
22 const validate = useMemo(
22 const validate = useMemo(
23 () =>
23 () =>
24 composeValidator({
24 composeValidator({
25− privateKey: buildHexValidator(64),
25+ privateKey: buildPrivateKeyValidator(64),
26 }),
26 }),
27 []
27 []
28 );
28 );
@@ -47,7 +47,7 @@ export default function PrivateKey({ className, goHome }) {
47 <>
47 <>
48 <Grid.Item
48 <Grid.Item
49 full
49 full
50− as={HexInput}
50+ as={PrivateKeyInput}
51 name="privateKey"
51 name="privateKey"
52 label="Private key"
52 label="Private key"
53 />
53 />