on master help sign in
Merge pull request #371 from urbit/reticket-metamask

fix reticket flow to handle noncustodial wallets

commit69a57bc031cd12be662eceee23bb144d01b76bad authorFang <git@fang.io> dateFri 31 Jan 2020, 11:38 (7 years ago) parent6ae4f030466486
6 files changed+84−27
src/lib/reticket.js+48−24 src/lib/tank.js+7 src/lib/txn.js+12 src/lib/useEthereumTransaction.js+1 src/views/Admin/Reticket/ReticketExecute.js+11−1 src/views/Invite/InviteEmail.js+5−2
src/lib/reticket.js+48−24
@@ -1,4 +1,3 @@
1−import Tx from 'ethereumjs-tx';
21import * as azimuth from 'azimuth-js';
32import * as tank from './tank';
43
@@ -9,7 +8,11 @@ import {
98 CRYPTO_SUITE_VERSION,
109} from './keys';
1110import { addHexPrefix } from './wallet';
12−import { sendAndAwaitAll } from './txn';
11+import {
12+ sendAndAwaitAllSerial,
13+ signTransaction,
14+ sendSignedTransaction,
15+} from './txn';
1316import getSuggestedGasPrice from './getSuggestedGasPrice';
1417import { GAS_LIMITS } from './constants';
1518import { toBN } from 'web3-utils';
@@ -29,6 +32,8 @@ export const TRANSACTION_PROGRESS = {
2932
3033export async function reticketPointBetweenWallets({
3134 fromWallet,
35+ fromWalletType,
36+ fromWalletHdPath,
3237 toWallet,
3338 point,
3439 web3,
@@ -144,26 +149,37 @@ export async function reticketPointBetweenWallets({
144149 progress(TRANSACTION_PROGRESS.SIGNING);
145150
146151 const suggestedGasPrice = await getSuggestedGasPrice(networkType);
152+ const chainId = await web3.eth.net.getId();
147153 const gasPrice = safeToWei(suggestedGasPrice.toFixed(), 'gwei');
148154 const gasPriceBN = toBN(gasPrice);
149− let totalCost = toBN(0);
150155 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
151− txs = txs.map(tx => {
152− tx.from = fromWallet.address;
153− tx.nonce = inviteNonce++;
154− tx.gasPrice = gasPriceBN;
155− totalCost = totalCost.add(gasPriceBN.mul(toBN(tx.gas)));
156− return tx;
157− });
156+ const totalCost = txs.reduce(
157+ (acc, tx) => acc.add(gasPriceBN.mul(toBN(tx.gas))),
158+ toBN(0)
159+ );
158160
159− let txPairs = txs.map(tx => {
160− let stx = new Tx(tx);
161− stx.sign(Buffer.from(fromWallet.privateKey, 'hex'));
162− return {
161+ let txPairs = [];
162+
163+ // Must be done in serial else hardware will give nonce errors
164+ for (let i = 0; i < txs.length; i++) {
165+ const stx = await signTransaction({
166+ wallet: fromWallet,
167+ walletType: fromWalletType,
168+ walletHdPath: fromWalletHdPath,
169+ networkType,
170+ txn: txs[i],
171+ nonce: inviteNonce + i,
172+ chainId,
173+ gasPrice: suggestedGasPrice.toFixed(),
174+ gasLimit: txs[i].gas,
175+ });
176+ txPairs.push({
163177 raw: hexify(stx.serialize()),
164178 signed: stx,
165− };
166− });
179+ });
180+ }
181+
182+ inviteNonce = inviteNonce + txs.length;
167183
168184 //
169185 // ensuring funding for transactions
@@ -175,6 +191,7 @@ export async function reticketPointBetweenWallets({
175191 web3,
176192 point,
177193 fromWallet.address,
194+ fromWalletType,
178195 totalCost,
179196 txPairs.map(p => p.raw),
180197 askForFunding,
@@ -187,7 +204,7 @@ export async function reticketPointBetweenWallets({
187204
188205 progress(TRANSACTION_PROGRESS.TRANSFERRING);
189206
190− await sendAndAwaitAll(web3, txPairs.map(p => p.signed), usedTank);
207+ await sendAndAwaitAllSerial(web3, txPairs.map(p => p.signed), usedTank);
191208
192209 //
193210 // move leftover eth
@@ -201,17 +218,24 @@ export async function reticketPointBetweenWallets({
201218 const sendEthCost = gasPriceBN.mul(toBN(gasLimit));
202219 if (transferEth && balance.gt(sendEthCost)) {
203220 const value = balance.sub(sendEthCost);
204− const tx = {
221+ const txn = {
205222 to: toWallet.ownership.keys.address,
206223 value: value,
224+ };
225+ const stx = signTransaction({
226+ wallet: fromWallet,
227+ walletType: fromWalletType,
228+ walletHdPath: fromWalletHdPath,
229+ txn,
230+ chainId,
231+ networkType,
207232 gasPrice: gasPriceBN,
208− gas: gasLimit,
233+ gasLimit,
209234 nonce: inviteNonce++,
210− };
211− let stx = new Tx(tx);
212− stx.sign(fromWallet.privateKey);
213− const rawTx = hexify(stx.serialize());
214− web3.eth.sendSignedTransaction(rawTx).catch(err => {
235+ });
236+ // let stx = new Tx(tx);
237+ // stx.sign(fromWallet.privateKey);
238+ sendSignedTransaction(web3, stx).catch(err => {
215239 console.log('error sending value tx, who cares', err);
216240 });
217241 }
src/lib/tank.js+7
@@ -3,6 +3,7 @@
33import retry from 'async-retry';
44import { toBN } from 'web3-utils';
55import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
6+import { WALLET_TYPES } from 'lib/wallet';
67
78//NOTE if accessing this in a localhost configuration fails with "CORS request
89// did not succeed", you might need to visit localhost:3001 or whatever
@@ -49,6 +50,7 @@ const ensureFundsFor = async (
4950 web3,
5051 point,
5152 address,
53+ walletType,
5254 cost,
5355 signedTxs,
5456 askForFunding,
@@ -66,6 +68,11 @@ const ensureFundsFor = async (
6668 return false;
6769 }
6870
71+ if (walletType === WALLET_TYPES.METAMASK) {
72+ console.log('tank: disabling for metamask login');
73+
74+ return false;
75+ }
6976 try {
7077 // TODO: if we can't always (easily) provide a point, and the
7178 // fundTransactions call is gonna fail anyway, should we maybe
src/lib/txn.js+12
@@ -173,6 +173,17 @@ const sendAndAwaitAll = (web3, stxs, doubtNonceError) => {
173173 );
174174};
175175
176+const sendAndAwaitAllSerial = (web3, stxs, doubtNonceError) => {
177+ return stxs.reduce(
178+ (promise, stx) =>
179+ promise.then(async () => {
180+ const txHash = await sendSignedTransaction(web3, stx, doubtNonceError);
181+ await waitForTransactionConfirm(web3, txHash);
182+ }),
183+ Promise.resolve()
184+ );
185+};
186+
176187const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
177188 Promise.all(signedTxs.map(tx => sendSignedTransaction(web3, tx, usedTank)));
178189
@@ -215,6 +226,7 @@ export {
215226 waitForTransactionConfirm,
216227 sendTransactionsAndAwaitConfirm,
217228 sendAndAwaitAll,
229+ sendAndAwaitAllSerial,
218230 getTxnInfo,
219231 hexify,
220232 renderSignedTx,
src/lib/useEthereumTransaction.js+1
@@ -158,6 +158,7 @@ export default function useEthereumTransaction(
158158 _web3,
159159 pointCursor.value,
160160 _wallet.address,
161+ walletType,
161162 cost,
162163 rawTxs,
163164 (address, minBalance, balance) =>
src/views/Admin/Reticket/ReticketExecute.js+11−1
@@ -44,7 +44,14 @@ const labelForProgress = progress => {
4444export default function ReticketExecute({ newWallet, setNewWallet }) {
4545 const { popTo, names, reset } = useHistory();
4646 const { web3, contracts, networkType } = useNetwork();
47− const { wallet, setWalletType, resetWallet, setUrbitWallet } = useWallet();
47+ const {
48+ wallet,
49+ setWalletType,
50+ resetWallet,
51+ setUrbitWallet,
52+ walletType,
53+ walletHdPath,
54+ } = useWallet();
4855 const { pointCursor } = usePointCursor();
4956 const { getDetails } = usePointCache();
5057
@@ -64,6 +71,8 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
6471 try {
6572 await reticketPointBetweenWallets({
6673 fromWallet: need.wallet(wallet),
74+ fromWalletType: walletType,
75+ fromWalletHdPath: walletHdPath,
6776 toWallet: newWallet.value.wallet,
6877 point: point,
6978 web3: need.web3(web3),
@@ -73,6 +82,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
7382 nextRevision: networkRevision + 1,
7483 });
7584 } catch (err) {
85+ console.error(err);
7686 setGeneralError(err);
7787 }
7888 })();
src/views/Invite/InviteEmail.js+5−2
@@ -316,6 +316,7 @@ export default function InviteEmail() {
316316 _web3,
317317 point,
318318 _wallet.address,
319+ walletType,
319320 toWei(totalCost.toString(), 'gwei'),
320321 names.map(name => invites[name].rawTx),
321322 (address, minBalance, balance) =>
@@ -384,12 +385,14 @@ export default function InviteEmail() {
384385 }, [
385386 web3,
386387 wallet,
387− point,
388+ walletType,
388389 gasPrice,
389− invites,
390+ point,
390391 clearReceipts,
392+ invites,
391393 addReceipt,
392394 sendMail,
395+ message,
393396 ]);
394397
395398 const onSubmit = useCallback(