on master help sign in
Merge branch 'mino' into feat/indigo-admin
41620dfMatt Condon (shrugs)
package-lock.json+13
@@ -2573,6 +2573,14 @@
2573 "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
2573 "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
2574 "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
2574 "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
2575 },
2575 },
2576+ "async-retry": {
2577+ "version": "1.2.3",
2578+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.2.3.tgz",
2579+ "integrity": "sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q==",
2580+ "requires": {
2581+ "retry": "0.12.0"
2582+ }
2583+ },
2576 "asynckit": {
2584 "asynckit": {
2577 "version": "0.4.0",
2585 "version": "0.4.0",
2578 "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
2586 "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -14927,6 +14935,11 @@
14927 "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
14935 "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
14928 "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
14936 "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
14929 },
14937 },
14938+ "retry": {
14939+ "version": "0.12.0",
14940+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
14941+ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
14942+ },
14930 "rgb-regex": {
14943 "rgb-regex": {
14931 "version": "1.0.1",
14944 "version": "1.0.1",
14932 "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
14945 "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
package.json+1
@@ -18,6 +18,7 @@
18 "@ledgerhq/hw-app-eth": "^4.61.0",
18 "@ledgerhq/hw-app-eth": "^4.61.0",
19 "@ledgerhq/hw-transport-u2f": "^4.61.0",
19 "@ledgerhq/hw-transport-u2f": "^4.61.0",
20 "PaperCollateralRenderer": "github:urbit/PaperCollateralRenderer",
20 "PaperCollateralRenderer": "github:urbit/PaperCollateralRenderer",
21+ "async-retry": "^1.2.3",
21 "azimuth-js": "^0.15.0",
22 "azimuth-js": "^0.15.0",
22 "azimuth-solidity": "^1.2.0",
23 "azimuth-solidity": "^1.2.0",
23 "bip32": "^1.0.2",
24 "bip32": "^1.0.2",
src/Bridge.js+4−1
@@ -12,6 +12,7 @@ import { ROUTES } from 'lib/router';
12import { NETWORK_TYPES } from 'lib/network';
12import { NETWORK_TYPES } from 'lib/network';
13import { walletFromMnemonic } from 'lib/wallet';
13import { walletFromMnemonic } from 'lib/wallet';
14import { isDevelopment } from 'lib/flags';
14import { isDevelopment } from 'lib/flags';
15+import { hasDisclaimed } from 'lib/disclaimerCookie';
15
16
16import 'style/index.scss';
17import 'style/index.scss';
17
18
@@ -29,7 +30,9 @@ const INITIAL_ROUTES = IS_STUBBED
29 { key: ROUTE_NAMES.POINTS },
30 { key: ROUTE_NAMES.POINTS },
30 { key: ROUTE_NAMES.POINT },
31 { key: ROUTE_NAMES.POINT },
31 ]
32 ]
33+ : hasDisclaimed()
32− : [{ key: ROUTE_NAMES.LANDING }];
34+ ? [{ key: ROUTE_NAMES.LANDING }]
35+ : [{ key: ROUTE_NAMES.DISCLAIMER }];
33
36
34const INITIAL_WALLET = IS_STUBBED
37const INITIAL_WALLET = IS_STUBBED
35 ? walletFromMnemonic(
38 ? walletFromMnemonic(
src/components/old/StatelessTransaction.js+11−9
@@ -8,6 +8,7 @@ import { Button } from './Base';
8import { CheckboxButton, Input, InnerLabel, InnerLabelDropdown } from './Base';
8import { CheckboxButton, Input, InnerLabel, InnerLabelDropdown } from './Base';
9import { Warning } from './Base';
9import { Warning } from './Base';
10
10
11+import { GAS_LIMITS } from '../../lib/constants';
11import { BRIDGE_ERROR } from '../../lib/error';
12import { BRIDGE_ERROR } from '../../lib/error';
12import { ROUTE_NAMES } from '../../lib/routeNames';
13import { ROUTE_NAMES } from '../../lib/routeNames';
13import { compose } from '../../lib/lib';
14import { compose } from '../../lib/lib';
@@ -40,7 +41,7 @@ class StatelessTransaction extends React.Component {
40
41
41 this.state = {
42 this.state = {
42 gasPrice: '5',
43 gasPrice: '5',
43− gasLimit: '600000',
44+ gasLimit: GAS_LIMITS.DEFAULT.toString(),
44 showGasDetails: false,
45 showGasDetails: false,
45 userApproval: false,
46 userApproval: false,
46 chainId: '1',
47 chainId: '1',
@@ -184,7 +185,7 @@ class StatelessTransaction extends React.Component {
184 const stx = state.stx.matchWith({
185 const stx = state.stx.matchWith({
185 Just: tx => tx.value,
186 Just: tx => tx.value,
186 Nothing: () => {
187 Nothing: () => {
187− throw BRIDGE_ERROR.MISSING_TXN;
188+ throw new Error(BRIDGE_ERROR.MISSING_TXN);
188 },
189 },
189 });
190 });
190
191
@@ -232,12 +233,7 @@ class StatelessTransaction extends React.Component {
232 } else {
233 } else {
233 this.setState({ txStatus: SUBMISSION_STATES.SENDING });
234 this.setState({ txStatus: SUBMISSION_STATES.SENDING });
234
235
235− sendSignedTransaction(
236− web3,
237− state.stx,
238− usedTank,
239− props.setTxnConfirmations
236+ sendSignedTransaction(web3, stx, usedTank, props.setTxnConfirmations)
240− )
241 .then(txHash => {
237 .then(txHash => {
242 props.setTxnCursor(Just(Ok(txHash)));
238 props.setTxnCursor(Just(Ok(txHash)));
243
239
@@ -498,7 +494,13 @@ class StatelessTransaction extends React.Component {
498 prop-size={'lg wide'}
494 prop-size={'lg wide'}
499 prop-color={signerButtonColor}
495 prop-color={signerButtonColor}
500 onClick={() =>
496 onClick={() =>
497+ signTransaction({
498+ ...this.props,
499+ ...this.state,
501− signTransaction({ ...this.props, ...this.state, setStx: this.setStx })
500+ setStx: this.setStx,
501+ wallet: this.props.wallet.value,
502+ txn: txn.value,
503+ })
502 }>
504 }>
503 {'Sign Transaction'}
505 {'Sign Transaction'}
504 </Button>
506 </Button>
src/lib/constants.js+2−2
@@ -20,7 +20,7 @@ const SEED_ENTROPY_BITS = 128;
20//TODO move into azimuth-js
20//TODO move into azimuth-js
21const GAS_LIMITS = {
21const GAS_LIMITS = {
22 SPAWN: 250000,
22 SPAWN: 250000,
23− TRANSFER: 490000,
23+ TRANSFER: 550000,
24 CONFIGURE_KEYS: 100000,
24 CONFIGURE_KEYS: 100000,
25 SET_PROXY: 120000,
25 SET_PROXY: 120000,
26 //
26 //
@@ -32,7 +32,7 @@ const GAS_LIMITS = {
32 //
32 //
33 GIFT_PLANET: 400000, //NOTE low sample size
33 GIFT_PLANET: 400000, //NOTE low sample size
34 //
34 //
35− DEFAULT: 500000,
35+ DEFAULT: 550000,
36};
36};
37
37
38// TODO: this is walletgen-ui specific, move into a wallet router later
38// TODO: this is walletgen-ui specific, move into a wallet router later
src/lib/disclaimerCookie.jsnew file+9
@@ -0,0 +1,9 @@
1+const COOKIE_ID = 'bridge_disclaimed';
2+
3+export const hasDisclaimed = () => {
4+ return document.cookie.includes(COOKIE_ID);
5+};
6+
7+export const setDisclaimerCookie = () => {
8+ document.cookie = COOKIE_ID;
9+};
src/lib/error.js+11−11
@@ -1,15 +1,15 @@
1const BRIDGE_ERROR = {
1const BRIDGE_ERROR = {
2− MISSING_WEB3: new Error('no web3 object found'),
2+ MISSING_WEB3: 'no web3 object found',
3− MISSING_WALLET: new Error('no wallet found'),
3+ MISSING_WALLET: 'no wallet found',
4− MISSING_TXN: new Error('no transaction found'),
4+ MISSING_TXN: 'no transaction found',
5− MISSING_CONTRACTS: new Error('no contracts found'),
5+ MISSING_CONTRACTS: 'no contracts found',
6− MISSING_KEYSTORE: new Error('no keystore text found'),
6+ MISSING_KEYSTORE: 'no keystore text found',
7− MISSING_MNEMONIC: new Error('no authentication mnemonic found'),
7+ MISSING_MNEMONIC: 'no authentication mnemonic found',
8− MISSING_URBIT_WALLET: new Error('no urbit wallet found'),
8+ MISSING_URBIT_WALLET: 'no urbit wallet found',
9− MISSING_POINT: new Error('no point found'),
9+ MISSING_POINT: 'no point found',
10− INVALID_ROUTE: new Error('invalid route'),
10+ INVALID_ROUTE: 'invalid route',
11− INVALID_NETWORK_TYPE: new Error('invalid network type'),
11+ INVALID_NETWORK_TYPE: 'invalid network type',
12− INVALID_WALLET_TYPE: new Error('invalid wallet type'),
12+ INVALID_WALLET_TYPE: 'invalid wallet type',
13};
13};
14
14
15const renderTxnError = (web3, msg) => {
15const renderTxnError = (web3, msg) => {
src/lib/invite.js+3−8
@@ -1,6 +1,4 @@
1−import { Just } from 'folktale/maybe';
2import Tx from 'ethereumjs-tx';
1import Tx from 'ethereumjs-tx';
3−
4import * as azimuth from 'azimuth-js';
2import * as azimuth from 'azimuth-js';
5import * as kg from 'urbit-key-generation/dist';
3import * as kg from 'urbit-key-generation/dist';
6import * as wg from './walletgen';
4import * as wg from './walletgen';
@@ -9,9 +7,10 @@ import * as tank from './tank';
9import JSZip from 'jszip';
7import JSZip from 'jszip';
10import saveAs from 'file-saver';
8import saveAs from 'file-saver';
11
9
12−import { hexify, sendTransactionsAndAwaitConfirm } from './txn';
10+import { hexify } from './txn';
13import { deriveNetworkSeedFromUrbitWallet } from './keys';
11import { deriveNetworkSeedFromUrbitWallet } from './keys';
14import { addHexPrefix } from './wallet';
12import { addHexPrefix } from './wallet';
13+import { sendAndAwaitAll } from './txn';
15import { GAS_LIMITS } from './constants';
14import { GAS_LIMITS } from './constants';
16
15
17// the initial network key revision is always 1
16// the initial network key revision is always 1
@@ -239,11 +238,7 @@ export async function reticketPointBetweenWallets({
239
238
240 progress(TRANSACTION_STATES.TRANSFERRING);
239 progress(TRANSACTION_STATES.TRANSFERRING);
241
240
242− await sendTransactionsAndAwaitConfirm(
243− web3,
244− txPairs.map(p => Just(p.signed)),
241+ await sendAndAwaitAll(web3, txPairs.map(p => p.signed), usedTank);
245− usedTank
246− );
247
242
248 //
243 //
249 // move leftover eth
244 // move leftover eth
src/lib/need.js+6−6
@@ -30,26 +30,26 @@ export const web3 = needBuilder(() => {
30});
30});
31
31
32export const contracts = needBuilder(() => {
32export const contracts = needBuilder(() => {
33− throw new Error(BRIDGE_ERROR.MISSING_CONTRACTS.message);
33+ throw new Error(BRIDGE_ERROR.MISSING_CONTRACTS);
34});
34});
35
35
36export const wallet = needBuilder(() => {
36export const wallet = needBuilder(() => {
37− throw new Error(BRIDGE_ERROR.MISSING_WALLET.message);
37+ throw new Error(BRIDGE_ERROR.MISSING_WALLET);
38});
38});
39
39
40export const addressFromWallet = obj => wallet(obj).address;
40export const addressFromWallet = obj => wallet(obj).address;
41
41
42export const point = needBuilder(() => {
42export const point = needBuilder(() => {
43− throw new Error(BRIDGE_ERROR.MISSING_POINT.message);
43+ throw new Error(BRIDGE_ERROR.MISSING_POINT);
44});
44});
45
45
46export const pointCache = needBuilder(() => {
46export const pointCache = needBuilder(() => {
47− throw new Error(BRIDGE_ERROR.MISSING_POINT.message);
47+ throw new Error(BRIDGE_ERROR.MISSING_POINT);
48});
48});
49
49
50export const fromPointCache = (cache, point) => {
50export const fromPointCache = (cache, point) => {
51 if (!(point in cache)) {
51 if (!(point in cache)) {
52− throw new Error(BRIDGE_ERROR.MISSING_POINT.message);
52+ throw new Error(BRIDGE_ERROR.MISSING_POINT);
53 }
53 }
54
54
55 return cache[point];
55 return cache[point];
@@ -60,7 +60,7 @@ export const keystore = obj => {
60 return ks.value.matchWith({
60 return ks.value.matchWith({
61 Ok: result => result.value,
61 Ok: result => result.value,
62 Error: _ => {
62 Error: _ => {
63− throw BRIDGE_ERROR.MISSING_KEYSTORE;
63+ throw new Error(BRIDGE_ERROR.MISSING_KEYSTORE);
64 },
64 },
65 });
65 });
66};
66};
src/lib/routeNames.js+1
@@ -1,5 +1,6 @@
1export const ROUTE_NAMES = {
1export const ROUTE_NAMES = {
2 DEFAULT: Symbol('DEFAULT'),
2 DEFAULT: Symbol('DEFAULT'),
3+ DISCLAIMER: Symbol('DISCLAIMER'),
3 //
4 //
4 ACTIVATE: Symbol('ACTIVATE'),
5 ACTIVATE: Symbol('ACTIVATE'),
5 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
6 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
src/lib/router.js+2
@@ -1,4 +1,5 @@
1import Landing from '../views/Landing';
1import Landing from '../views/Landing';
2+import Disclaimer from '../views/Disclaimer';
2import Login from '../views/Login.js';
3import Login from '../views/Login.js';
3import InvitesManage from '../views/InvitesManage.js';
4import InvitesManage from '../views/InvitesManage.js';
4import Admin from '../views/Admin';
5import Admin from '../views/Admin';
@@ -24,6 +25,7 @@ import { ROUTE_NAMES } from './routeNames';
24
25
25export const ROUTES = {
26export const ROUTES = {
26 [ROUTE_NAMES.LANDING]: Landing,
27 [ROUTE_NAMES.LANDING]: Landing,
28+ [ROUTE_NAMES.DISCLAIMER]: Disclaimer,
27 [ROUTE_NAMES.ACTIVATE]: Activate,
29 [ROUTE_NAMES.ACTIVATE]: Activate,
28 [ROUTE_NAMES.INVITE]: Invite,
30 [ROUTE_NAMES.INVITE]: Invite,
29 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
31 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
src/lib/tank.js+18−29
@@ -1,7 +1,7 @@
1// /lib/tank: functions for funding transactions
1// /lib/tank: functions for funding transactions
2
2
3+import retry from 'async-retry';
3−import { waitForTransactionConfirm } from './txn';
4+import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
4−import { CHECK_BLOCK_EVERY_MS } from './constants';
5
5
6//NOTE if accessing this in a localhost configuration fails with "CORS request
6//NOTE if accessing this in a localhost configuration fails with "CORS request
7// did not succeed", you might need to visit localhost:3001 or whatever
7// did not succeed", you might need to visit localhost:3001 or whatever
@@ -93,40 +93,29 @@ const ensureFundsFor = async (
93 return false;
93 return false;
94};
94};
95
95
96−// resolves when address has at least minBalance
96+// returns a promise that resolves when address has at least minBalance
97−//
98−//TODO should maybe do a "we got it" callback also, so clients can hide msg?
99−async function waitForBalance(
97+function waitForBalance(web3, address, minBalance, askForFunding, gotFunding) {
100− web3,
98+ let oldBalance = null;
101− address,
99+ return retry(async (bail, n) => {
102− minBalance,
103− askForFunding,
104− gotFunding
105−) {
106− console.log('tank: awaiting balance', address, minBalance);
107− return new Promise((resolve, reject) => {
108− const checkForBalance = async () => {
109− const balance = await web3.eth.getBalance(address);
100+ const balance = await web3.eth.getBalance(address);
110−
111− if (balance >= minBalance) {
101+ if (balance >= minBalance) {
102+ // if we ever asked for funding, retract that request now
112− if (gotFunding) {
103+ if (gotFunding && oldBalance !== null) {
113− gotFunding();
104+ gotFunding();
114− }
105+ }
115−
106+ return;
116− resolve();
117− } else {
107+ } else {
108+ if (balance !== oldBalance) {
118 // TODO: minBalance is a `number` type
109 // TODO: minBalance is a `number` type
119 // but we want to display ETH, which will never accept a number
110 // but we want to display ETH, which will never accept a number
120 // but instead wants string or BN/BigNumber.
111 // but instead wants string or BN/BigNumber.
121 // this will be heavily refactored to correctly do BN math, but until
112 // this will be heavily refactored to correctly do BN math, but until
122 // then we'll manually convert this number to an integer string
113 // then we'll manually convert this number to an integer string
123 askForFunding(address, minBalance.toFixed(), balance);
114 askForFunding(address, minBalance.toFixed(), balance);
124− setTimeout(checkForBalance, CHECK_BLOCK_EVERY_MS);
125 }
115 }
126− };
116+ throw new Error('retrying');
127−
117+ }
128− checkForBalance();
118+ }, RETRY_OPTIONS);
129− });
130}
119}
131
120
132export { remainingTransactions, fundTransactions, ensureFundsFor };
121export { remainingTransactions, fundTransactions, ensureFundsFor };
src/lib/txn.js+44−70
@@ -2,14 +2,21 @@ import * as ob from 'urbit-ob';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import Tx from 'ethereumjs-tx';
3import Tx from 'ethereumjs-tx';
4import { toWei, fromWei, toHex } from 'web3-utils';
4import { toWei, fromWei, toHex } from 'web3-utils';
5+import retry from 'async-retry';
5
6
6−import { BRIDGE_ERROR } from './error';
7import { NETWORK_TYPES } from './network';
7import { NETWORK_TYPES } from './network';
8import { ledgerSignTransaction } from './ledger';
8import { ledgerSignTransaction } from './ledger';
9import { trezorSignTransaction } from './trezor';
9import { trezorSignTransaction } from './trezor';
10import { WALLET_TYPES, addHexPrefix } from './wallet';
10import { WALLET_TYPES, addHexPrefix } from './wallet';
11import { CHECK_BLOCK_EVERY_MS } from './constants';
11import { CHECK_BLOCK_EVERY_MS } from './constants';
12
12
13+const RETRY_OPTIONS = {
14+ retries: 99999,
15+ factor: 1,
16+ minTimeout: CHECK_BLOCK_EVERY_MS,
17+ randomize: false,
18+};
19+
13const signTransaction = async config => {
20const signTransaction = async config => {
14 let {
21 let {
15 wallet,
22 wallet,
@@ -24,6 +31,7 @@ const signTransaction = async config => {
24 gasLimit,
31 gasLimit,
25 } = config;
32 } = config;
26
33
34+ //TODO require these in txn object
27 nonce = toHex(nonce);
35 nonce = toHex(nonce);
28 chainId = toHex(chainId);
36 chainId = toHex(chainId);
29 gasPrice = toHex(toWei(gasPrice, 'gwei'));
37 gasPrice = toHex(toWei(gasPrice, 'gwei'));
@@ -74,47 +82,27 @@ const signTransaction = async config => {
74 ? Object.assign(txParams, eip155Params)
82 ? Object.assign(txParams, eip155Params)
75 : txParams;
83 : txParams;
76
84
77− const wal = wallet.matchWith({
78− Just: w => w.value,
79− Nothing: () => {
80− throw BRIDGE_ERROR.MISSING_WALLET;
81− },
82− });
83−
84− const sec = wal.privateKey;
85−
86− const utx = txn.matchWith({
87− Just: tx => Object.assign(tx.value, signingParams),
85+ const utx = Object.assign(txn, signingParams);
88− Nothing: () => {
89− throw BRIDGE_ERROR.MISSING_TXN;
90− },
91− });
92
86
93 const stx = new Tx(utx);
87 const stx = new Tx(utx);
94
88
89+ //TODO should try-catch and display error message to user,
90+ // ie ledger's "pls enable contract data"
91+ // needs to maybe happen at call-site though
95 if (walletType === WALLET_TYPES.LEDGER) {
92 if (walletType === WALLET_TYPES.LEDGER) {
96 await ledgerSignTransaction(stx, walletHdPath);
93 await ledgerSignTransaction(stx, walletHdPath);
97 } else if (walletType === WALLET_TYPES.TREZOR) {
94 } else if (walletType === WALLET_TYPES.TREZOR) {
98 await trezorSignTransaction(stx, walletHdPath);
95 await trezorSignTransaction(stx, walletHdPath);
99 } else {
96 } else {
100− stx.sign(sec);
97+ stx.sign(wallet.privateKey);
101 }
98 }
102
99
103 setStx(Just(stx));
100 setStx(Just(stx));
104 return stx;
101 return stx;
105};
102};
106
103
107−// TODO(shrugs): refactor the hell out of all of these functions
108−// but especially this one
109−const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
104+const sendSignedTransaction = (web3, stx, doubtNonceError) => {
110− const txn = stx.matchWith({
111− Just: tx => tx.value,
112− Nothing: () => {
113− throw BRIDGE_ERROR.MISSING_TXN;
114− },
115− });
116−
117− const rawTx = hexify(txn.serialize());
105+ const rawTx = hexify(stx.serialize());
118
106
119 return new Promise(async (resolve, reject) => {
107 return new Promise(async (resolve, reject) => {
120 web3.eth
108 web3.eth
@@ -122,39 +110,25 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
122 .on('transactionHash', hash => {
110 .on('transactionHash', hash => {
123 resolve(hash);
111 resolve(hash);
124 })
112 })
125− //TODO do we also reach this if network is slow? web3 only tries a set
126− // amount of times... should we instead do waitForTransactionConfirm
127− // in on-transactionHash? we don't care (much) about additional
128− // confirms anyway.
129− .on('confirmation', (confirmationNum, txn) => {
130− if (confirmationCb) {
131− confirmationCb(txn.transactionHash, confirmationNum + 1);
132− }
133− resolve(txn.transactionHash);
134− })
135 .on('error', err => {
113 .on('error', err => {
136 // if there's a nonce error, but we used the gas tank, it's likely
114 // if there's a nonce error, but we used the gas tank, it's likely
137 // that it's because the tank already submitted our transaction.
115 // that it's because the tank already submitted our transaction.
138 // we just wait for first confirmation here.
116 // we just wait for first confirmation here.
139 console.error(err);
117 console.error(err);
140− if (
118+ const isKnownError = (err.message || '').includes(
141− doubtNonceError &&
119+ 'known transaction: '
120+ );
121+ const isNonceError = (err.message || '').includes(
142− (err.message || '').includes("the tx doesn't have the correct nonce.")
122+ "the tx doesn't have the correct nonce."
143− ) {
123+ );
124+ if (isKnownError || (doubtNonceError && isNonceError)) {
125+ console.log(
144− console.log('nonce error, likely from gas tank submission, ignoring');
126+ 'tx send error likely from gas tank submission, ignoring'
127+ );
145 const txHash = web3.utils.keccak256(rawTx);
128 const txHash = web3.utils.keccak256(rawTx);
146− //TODO can we do does-exists check first?
147− //TODO max wait time before assume fail?
148− waitForTransactionConfirm(web3, txHash).then(res => {
149− if (res) {
150− resolve(txHash);
129+ resolve(txHash);
151− confirmationCb(txHash, 1);
152− } else {
153− reject('Unexpected tx failure');
154− }
155− });
156 } else {
130 } else {
157− reject(err.message);
131+ reject(err.message || 'Transaction sending failed!');
158 }
132 }
159 });
133 });
160 });
134 });
@@ -162,23 +136,22 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
162
136
163// returns a Promise<bool>, where the bool indicates tx success/failure
137// returns a Promise<bool>, where the bool indicates tx success/failure
164const waitForTransactionConfirm = (web3, txHash) => {
138const waitForTransactionConfirm = (web3, txHash) => {
165− return new Promise((resolve, reject) => {
139+ return retry(async (bail, n) => {
166− const checkForConfirm = async () => {
167− console.log('checking for confirm', txHash);
168− const receipt = await web3.eth.getTransactionReceipt(txHash);
140+ const receipt = await web3.eth.getTransactionReceipt(txHash);
169− console.log('tried, got', receipt);
170− let confirmed = receipt !== null;
141+ const confirmed = receipt !== null;
171− if (confirmed) resolve(receipt.status === true);
142+ if (confirmed) return receipt.status === true;
172− else setTimeout(checkForConfirm, CHECK_BLOCK_EVERY_MS);
143+ else throw new Error('retrying');
173− };
144+ }, RETRY_OPTIONS);
174− checkForConfirm();
175− });
176};
145};
177
146
178−const isTransactionConfirmed = async (web3, txHash) => {
147+// returns a Promise that resolves when all stxs have been sent & confirmed
179− const receipt = await web3.eth.getTransactionReceipt(txHash);
148+const sendAndAwaitAll = (web3, stxs, doubtNonceError) => {
180− console.log('got confirm state', receipt !== null, receipt.confirmations);
149+ return Promise.all(
181− return receipt !== null;
150+ stxs.map(async tx => {
151+ const txHash = await sendSignedTransaction(web3, tx, doubtNonceError);
152+ await waitForTransactionConfirm(web3, txHash);
153+ })
154+ );
182};
155};
183
156
184const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
157const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
@@ -217,11 +190,12 @@ const canDecodePatp = p => {
217};
190};
218
191
219export {
192export {
193+ RETRY_OPTIONS,
220 signTransaction,
194 signTransaction,
221 sendSignedTransaction,
195 sendSignedTransaction,
222 waitForTransactionConfirm,
196 waitForTransactionConfirm,
223 sendTransactionsAndAwaitConfirm,
197 sendTransactionsAndAwaitConfirm,
224− isTransactionConfirmed,
198+ sendAndAwaitAll,
225 getTxnInfo,
199 getTxnInfo,
226 hexify,
200 hexify,
227 renderSignedTx,
201 renderSignedTx,
src/lib/useEthereumTransaction.js+3−3
@@ -60,7 +60,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
60 // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
60 // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
61
61
62 const txn = await signTransaction({
62 const txn = await signTransaction({
63− wallet, // wallet: _wallet,
63+ wallet: _wallet,
64 walletType,
64 walletType,
65 walletHdPath,
65 walletHdPath,
66 networkType,
66 networkType,
@@ -78,13 +78,13 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
78 setError(error);
78 setError(error);
79 }
79 }
80 }, [
80 }, [
81+ _wallet,
81 chainId,
82 chainId,
82 gasLimit,
83 gasLimit,
83 gasPrice,
84 gasPrice,
84 networkType,
85 networkType,
85 nonce,
86 nonce,
86 unsignedTransaction,
87 unsignedTransaction,
87− wallet,
88 walletHdPath,
88 walletHdPath,
89 walletType,
89 walletType,
90 ]);
90 ]);
@@ -105,7 +105,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
105 try {
105 try {
106 const txHash = await sendSignedTransaction(
106 const txHash = await sendSignedTransaction(
107 _web3,
107 _web3,
108− Just(signedTransaction),
108+ signedTransaction,
109 /* doubtNonceError */ false
109 /* doubtNonceError */ false
110 );
110 );
111
111
src/lib/useImpliedTicket.jsnew file+14
@@ -0,0 +1,14 @@
1+import * as ob from 'urbit-ob';
2+
3+import { prependSig } from './transformers';
4+
5+/**
6+ * pull the suggested ticket from the #hash in the url
7+ */
8+export default function useImpliedTicket() {
9+ const hash = window.location.hash.slice(1);
10+ const ticket = prependSig(hash);
11+ const isValid = ob.isValidPatq(ticket);
12+
13+ return isValid ? ticket : null;
14+}
src/lib/useMailer.js+6−10
@@ -9,14 +9,14 @@ const STUB_MAILER = process.env.REACT_APP_STUB_MAILER === 'true';
9function useHasReceivedCache() {
9function useHasReceivedCache() {
10 const [cache, addToCache] = useSetState();
10 const [cache, addToCache] = useSetState();
11
11
12− const getHasRecieved = useCallback(
12+ const getHasReceived = useCallback(
13 email => cache[email] || Nothing(), //
13 email => cache[email] || Nothing(), //
14 [cache]
14 [cache]
15 );
15 );
16
16
17 const syncHasReceivedForEmail = useCallback(
17 const syncHasReceivedForEmail = useCallback(
18 async email => {
18 async email => {
19− if (Just.hasInstance(getHasRecieved(email))) {
19+ if (Just.hasInstance(getHasReceived(email))) {
20 // never update the cache after we know about it
20 // never update the cache after we know about it
21 return;
21 return;
22 }
22 }
@@ -29,10 +29,10 @@ function useHasReceivedCache() {
29 const _hasReceived = await hasReceived(email);
29 const _hasReceived = await hasReceived(email);
30 addToCache({ [email]: Just(_hasReceived) });
30 addToCache({ [email]: Just(_hasReceived) });
31 },
31 },
32− [getHasRecieved, addToCache]
32+ [getHasReceived, addToCache]
33 );
33 );
34
34
35− return { getHasRecieved, syncHasReceivedForEmail };
35+ return { getHasReceived, syncHasReceivedForEmail };
36}
36}
37
37
38export default function useMailer(emails) {
38export default function useMailer(emails) {
@@ -43,14 +43,10 @@ export default function useMailer(emails) {
43 const _sendMail = useCallback(async (email, ticket, rawTx) => {
43 const _sendMail = useCallback(async (email, ticket, rawTx) => {
44 if (STUB_MAILER) {
44 if (STUB_MAILER) {
45 console.log(`${email} - ${ticket}`);
45 console.log(`${email} - ${ticket}`);
46− return;
46+ return true;
47 }
47 }
48
48
49− const mailSuccess = await sendMail(email, ticket, rawTx);
49+ return await sendMail(email, ticket, rawTx);
50−
51− if (!mailSuccess) {
52− throw new Error(`Internal mailing error when mailing ${email}`);
53− }
54 }, []);
50 }, []);
55
51
56 return { ...hasReceivedCache, sendMail: _sendMail };
52 return { ...hasReceivedCache, sendMail: _sendMail };
src/store/network.js+1−1
@@ -20,7 +20,7 @@ function _useNetwork(initialNetworkType = null) {
20
20
21 const setNetworkType = networkType => {
21 const setNetworkType = networkType => {
22 if (!includes(NETWORK_TYPES, networkType)) {
22 if (!includes(NETWORK_TYPES, networkType)) {
23− throw BRIDGE_ERROR.INVALID_NETWORK_TYPE;
23+ throw new Error(BRIDGE_ERROR.INVALID_NETWORK_TYPE);
24 }
24 }
25 _setNetworkType(networkType);
25 _setNetworkType(networkType);
26 };
26 };
src/store/wallet.js+1−1
@@ -44,7 +44,7 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
44 const setWalletType = useCallback(
44 const setWalletType = useCallback(
45 walletType => {
45 walletType => {
46 if (!includes(WALLET_TYPES, walletType)) {
46 if (!includes(WALLET_TYPES, walletType)) {
47− throw BRIDGE_ERROR.INVALID_WALLET_TYPE;
47+ throw new Error(BRIDGE_ERROR.INVALID_WALLET_TYPE);
48 }
48 }
49
49
50 _setWalletType(walletType);
50 _setWalletType(walletType);
src/views/Activate.js−2
@@ -5,7 +5,6 @@ import useRouter from 'lib/useRouter';
5import { ActivateFlowProvider } from './Activate/ActivateFlow';
5import { ActivateFlowProvider } from './Activate/ActivateFlow';
6import useActivateFlowState from './Activate/useActivateFlowState';
6import useActivateFlowState from './Activate/useActivateFlowState';
7import ActivateCode from './Activate/ActivateCode';
7import ActivateCode from './Activate/ActivateCode';
8−import ActivateDisclaimer from './Activate/ActivateDisclaimer';
9import { LocalRouterProvider } from 'lib/LocalRouter';
8import { LocalRouterProvider } from 'lib/LocalRouter';
10import ActivatePassport from './Activate/ActivatePassport';
9import ActivatePassport from './Activate/ActivatePassport';
11
10
@@ -17,7 +16,6 @@ const NAMES = {
17
16
18const VIEWS = {
17const VIEWS = {
19 [NAMES.CODE]: ActivateCode,
18 [NAMES.CODE]: ActivateCode,
20− [NAMES.DISCLAIMER]: ActivateDisclaimer,
21 [NAMES.PASSPORT]: ActivatePassport,
19 [NAMES.PASSPORT]: ActivatePassport,
22};
20};
23
21
src/views/Activate/ActivateCode.js+7−5
@@ -21,11 +21,13 @@ import { generateWallet } from 'lib/invite';
21import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
21import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
22import { useActivateFlow } from './ActivateFlow';
22import { useActivateFlow } from './ActivateFlow';
23import { useLocalRouter } from 'lib/LocalRouter';
23import { useLocalRouter } from 'lib/LocalRouter';
24+import useImpliedTicket from 'lib/useImpliedTicket';
24
25
25export default function ActivateCode() {
26export default function ActivateCode() {
26 const history = useHistory();
27 const history = useHistory();
27 const { names, push } = useLocalRouter();
28 const { names, push } = useLocalRouter();
28 const { contracts } = useNetwork();
29 const { contracts } = useNetwork();
30+ const impliedTicket = useImpliedTicket();
29 const [generalError, setGeneralError] = useState();
31 const [generalError, setGeneralError] = useState();
30 const [deriving, setDeriving] = useState(false);
32 const [deriving, setDeriving] = useState(false);
31 const {
33 const {
@@ -39,16 +41,14 @@ export default function ActivateCode() {
39 const [ticketInput, { pass: validTicket, data: ticket }] = useTicketInput({
41 const [ticketInput, { pass: validTicket, data: ticket }] = useTicketInput({
40 name: 'ticket',
42 name: 'ticket',
41 label: 'Code',
43 label: 'Code',
44+ initialValue: impliedTicket || '',
42 autoFocus: true,
45 autoFocus: true,
43 });
46 });
44
47
45 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
48 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
46 history,
49 history,
47 ]);
50 ]);
48− const goToDisclaimer = useCallback(() => push(names.DISCLAIMER), [
51+ const goToPassport = useCallback(() => push(names.PASSPORT), [names, push]);
49− names,
50− push,
51− ]);
52
52
53 const pass = derivedWallet.matchWith({
53 const pass = derivedWallet.matchWith({
54 Nothing: () => false,
54 Nothing: () => false,
@@ -91,6 +91,8 @@ export default function ActivateCode() {
91 'This invite code has multiple points available.\n' +
91 'This invite code has multiple points available.\n' +
92 "Once you've activated this point, activate the next with the same process."
92 "Once you've activated this point, activate the next with the same process."
93 );
93 );
94+ } else {
95+ setGeneralError(false);
94 }
96 }
95 } else {
97 } else {
96 setGeneralError(
98 setGeneralError(
@@ -137,7 +139,7 @@ export default function ActivateCode() {
137 className="mt4"
139 className="mt4"
138 disabled={!pass || deriving}
140 disabled={!pass || deriving}
139 accessory={deriving ? <Blinky /> : undefined}
141 accessory={deriving ? <Blinky /> : undefined}
140− onClick={goToDisclaimer}
142+ onClick={goToPassport}
141 solid
143 solid
142 full>
144 full>
143 {deriving && 'Deriving...'}
145 {deriving && 'Deriving...'}
src/views/Admin/AdminTransfer.js+1−1
@@ -1,5 +1,5 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2−import { Grid, P, Text, Input } from 'indigo-react';
2+import { Grid, Text, Input } from 'indigo-react';
3import { useLocalRouter } from 'lib/LocalRouter';
3import { useLocalRouter } from 'lib/LocalRouter';
4
4
5import ViewHeader from 'components/ViewHeader';
5import ViewHeader from 'components/ViewHeader';
src/views/Activate/ActivateDisclaimer.js → src/views/Disclaimer.jsrenamed+7−3
@@ -3,7 +3,8 @@ import cn from 'classnames';
3import { Grid, H3, B, Text, CheckboxInput } from 'indigo-react';
3import { Grid, H3, B, Text, CheckboxInput } from 'indigo-react';
4
4
5import { useCheckboxInput } from 'lib/useInputs';
5import { useCheckboxInput } from 'lib/useInputs';
6−import { useLocalRouter } from 'lib/LocalRouter';
6+import { useHistory } from 'store/history';
7+import { setDisclaimerCookie } from 'lib/disclaimerCookie';
7
8
8import View from 'components/View';
9import View from 'components/View';
9import { ForwardButton } from 'components/Buttons';
10import { ForwardButton } from 'components/Buttons';
@@ -12,13 +13,16 @@ import WarningBox from 'components/WarningBox';
12const TEXT_STYLE = 'f5';
13const TEXT_STYLE = 'f5';
13
14
14export default function ActivateDisclaimer() {
15export default function ActivateDisclaimer() {
15− const { push, names } = useLocalRouter();
16+ const { popAndPush, names } = useHistory();
16 const [understoodInput, { data: isUnderstood }] = useCheckboxInput({
17 const [understoodInput, { data: isUnderstood }] = useCheckboxInput({
17 name: 'checkbox',
18 name: 'checkbox',
18 label: 'I acknowledge and understand these rights',
19 label: 'I acknowledge and understand these rights',
19 });
20 });
20
21
21− const goToPassport = useCallback(() => push(names.PASSPORT), [push, names]);
22+ const goToPassport = useCallback(() => {
23+ setDisclaimerCookie();
24+ popAndPush(names.LANDING);
25+ }, [popAndPush, names]);
22
26
23 return (
27 return (
24 <View>
28 <View>
src/views/Invite/InviteEmail.js+42−16
@@ -1,6 +1,5 @@
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3−import { Just } from 'folktale/maybe';
4import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
5import {
4import {
6 Grid,
5 Grid,
@@ -101,7 +100,7 @@ export default function InviteEmail() {
101 const { syncInvites, getInvites } = usePointCache();
100 const { syncInvites, getInvites } = usePointCache();
102 const { pointCursor } = usePointCursor();
101 const { pointCursor } = usePointCursor();
103 const point = need.point(pointCursor);
102 const point = need.point(pointCursor);
104− const { getHasRecieved, syncHasReceivedForEmail, sendMail } = useMailer();
103+ const { getHasReceived, syncHasReceivedForEmail, sendMail } = useMailer();
105
104
106 const { availableInvites } = getInvites(point);
105 const { availableInvites } = getInvites(point);
107 const maxInvitesToSend = availableInvites.matchWith({
106 const maxInvitesToSend = availableInvites.matchWith({
@@ -142,14 +141,14 @@ export default function InviteEmail() {
142 () =>
141 () =>
143 inputConfigs.map(config => {
142 inputConfigs.map(config => {
144 config.disabled = !canInput;
143 config.disabled = !canInput;
145− const hasReceivedError = getHasRecieved(config.name).matchWith({
144+ const hasReceivedError = getHasReceived(config.name).matchWith({
146 Nothing: () => null, // loading
145 Nothing: () => null, // loading
147 Just: p => p.value && HAS_RECEIVED_TEXT,
146 Just: p => p.value && HAS_RECEIVED_TEXT,
148 });
147 });
149 config.error = hasReceivedError || errors[config.name];
148 config.error = hasReceivedError || errors[config.name];
150 return config;
149 return config;
151 }),
150 }),
152− [inputConfigs, errors, canInput, getHasRecieved]
151+ [inputConfigs, errors, canInput, getHasReceived]
153 );
152 );
154
153
155 // construct the state of the set of inputs we're rendering below
154 // construct the state of the set of inputs we're rendering below
@@ -187,6 +186,27 @@ export default function InviteEmail() {
187 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
186 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
188 }
187 }
189
188
189+ //TODO want to do this on-input, but that gets weird. see #188
190+ let errorCount = 0;
191+ await Promise.all(
192+ inputs.map(async input => {
193+ const email = input.data;
194+ const hasReceived = getHasReceived(email).matchWith({
195+ Nothing: () => false, // loading
196+ Just: p => p.value,
197+ });
198+ if (hasReceived) {
199+ errorCount++;
200+ addError({
201+ [input.name]: `${email} has already received an invite.`,
202+ });
203+ }
204+ })
205+ );
206+ if (errorCount > 0) {
207+ throw new Error(`Some of these already have a point!`);
208+ }
209+
190 const nonce = await _web3.eth.getTransactionCount(_wallet.address);
210 const nonce = await _web3.eth.getTransactionCount(_wallet.address);
191 const chainId = await _web3.eth.net.getId();
211 const chainId = await _web3.eth.net.getId();
192 const planets = await azimuth.delegatedSending.getPlanetsToSend(
212 const planets = await azimuth.delegatedSending.getPlanetsToSend(
@@ -207,7 +227,6 @@ export default function InviteEmail() {
207 );
227 );
208 }
228 }
209
229
210− let errorCount = 0;
211 clearInvites();
230 clearInvites();
212 // NB(shrugs) - must be processed in serial because main thread, etc
231 // NB(shrugs) - must be processed in serial because main thread, etc
213 for (let i = 0; i < inputs.length; i++) {
232 for (let i = 0; i < inputs.length; i++) {
@@ -227,12 +246,12 @@ export default function InviteEmail() {
227 owner.keys.address
246 owner.keys.address
228 );
247 );
229 const signedTx = await signTransaction({
248 const signedTx = await signTransaction({
230− wallet,
249+ wallet: _wallet,
231 walletType,
250 walletType,
232 walletHdPath,
251 walletHdPath,
233 networkType,
252 networkType,
234 // TODO: ^ make a useTransactionSigner to encapsulate this logic
253 // TODO: ^ make a useTransactionSigner to encapsulate this logic
235− txn: Just(inviteTx),
254+ txn: inviteTx,
236 gasPrice: GAS_PRICE_GWEI.toString(),
255 gasPrice: GAS_PRICE_GWEI.toString(),
237 gasLimit: GAS_LIMIT.toString(),
256 gasLimit: GAS_LIMIT.toString(),
238 nonce: nonce + i,
257 nonce: nonce + i,
@@ -258,18 +277,19 @@ export default function InviteEmail() {
258 );
277 );
259 }
278 }
260 }, [
279 }, [
261− point,
262 contracts,
280 contracts,
263 web3,
281 web3,
264− addInvite,
282+ wallet,
283+ inputs,
284+ point,
265 clearInvites,
285 clearInvites,
286+ getHasReceived,
287+ addError,
266 syncInvites,
288 syncInvites,
267− inputs,
268− networkType,
269− wallet,
270 walletType,
289 walletType,
271 walletHdPath,
290 walletHdPath,
272− addError,
291+ networkType,
292+ addInvite,
273 ]);
293 ]);
274
294
275 const sendInvites = useCallback(async () => {
295 const sendInvites = useCallback(async () => {
@@ -300,7 +320,7 @@ export default function InviteEmail() {
300 try {
320 try {
301 const txHash = await sendSignedTransaction(
321 const txHash = await sendSignedTransaction(
302 _web3,
322 _web3,
303− Just(invite.signedTx),
323+ invite.signedTx,
304 tankWasUsed
324 tankWasUsed
305 );
325 );
306
326
@@ -317,12 +337,18 @@ export default function InviteEmail() {
317 }
337 }
318
338
319 try {
339 try {
340+ const success = await sendMail(
341+ invite.email,
320− await sendMail(invite.email, invite.ticket, invite.rawTx);
342+ invite.ticket,
343+ invite.rawTx
344+ );
345+ if (!success) throw new Error('Failed to send mail');
321 } catch (error) {
346 } catch (error) {
322 console.error(error);
347 console.error(error);
323 errorCount++;
348 errorCount++;
349+ //TODO make sure this actually gets displayed
324 addError({
350 addError({
325− [input.name]: `Mailing Failure for ${invite.email}`,
351+ [input.name]: `Mailing Failure for ${invite.email}, please send them this code manually: ${invite.ticket}`,
326 });
352 });
327 }
353 }
328
354
src/views/Landing.js+14−5
@@ -1,14 +1,23 @@
1import React from 'react';
1import React from 'react';
2import { H1, P } from 'indigo-react';
2import { H1, P } from 'indigo-react';
3
3
4−import { ROUTE_NAMES } from '../lib/routeNames';
5import { useHistory } from '../store/history';
4import { useHistory } from '../store/history';
5+import useImpliedPoint from 'lib/useImpliedPoint';
6+import useImpliedTicket from 'lib/useImpliedTicket';
6
7
7import View from 'components/View';
8import View from 'components/View';
8import { ForwardButton } from 'components/Buttons';
9import { ForwardButton } from 'components/Buttons';
9
10
10function Landing() {
11function Landing() {
11− const history = useHistory();
12+ const { push, names } = useHistory();
13+
14+ const impliedPoint = useImpliedPoint();
15+ const impliedTicket = useImpliedTicket();
16+ if (impliedPoint) {
17+ push(names.LOGIN);
18+ } else if (impliedTicket) {
19+ push(names.ACTIVATE);
20+ }
12
21
13 return (
22 return (
14 <View>
23 <View>
@@ -22,21 +31,21 @@ function Landing() {
22 <ForwardButton
31 <ForwardButton
23 className="mt3"
32 className="mt3"
24 detail="If you were sent an Azimuth invite code."
33 detail="If you were sent an Azimuth invite code."
25− onClick={() => history.push(ROUTE_NAMES.ACTIVATE)}>
34+ onClick={() => push(names.ACTIVATE)}>
26 Activate a Point
35 Activate a Point
27 </ForwardButton>
36 </ForwardButton>
28
37
29 <ForwardButton
38 <ForwardButton
30 className="mt3"
39 className="mt3"
31 detail="Login to an activated point."
40 detail="Login to an activated point."
32− onClick={() => history.push(ROUTE_NAMES.LOGIN)}>
41+ onClick={() => push(names.LOGIN)}>
33 Login
42 Login
34 </ForwardButton>
43 </ForwardButton>
35
44
36 <ForwardButton
45 <ForwardButton
37 className="mt3"
46 className="mt3"
38 detail="View an Azimuth point without signing into a wallet."
47 detail="View an Azimuth point without signing into a wallet."
39− onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
48+ onClick={() => push(names.VIEW_POINT)}>
40 View a Point
49 View a Point
41 </ForwardButton>
50 </ForwardButton>
42 </View>
51 </View>
src/views/SentTransaction.js+1−1
@@ -125,7 +125,7 @@ function SentTransaction(props) {
125
125
126 const result = txnCursor.matchWith({
126 const result = txnCursor.matchWith({
127 Nothing: _ => {
127 Nothing: _ => {
128− throw BRIDGE_ERROR.MISSING_TXN;
128+ throw new Error(BRIDGE_ERROR.MISSING_TXN);
129 },
129 },
130 Just: res => res.value,
130 Just: res => res.value,
131 });
131 });