on master help sign in
package.json+1−1
since splitsnapshots
unifiedsplit
@@ -1,6 +1,6 @@
1{
1{
2 "name": "bridge",
2 "name": "bridge",
3− "version": "3.3.0",
3+ "version": "3.3.1",
4 "description": "An application for interacting with Azimuth",
4 "description": "An application for interacting with Azimuth",
5 "repository": {
5 "repository": {
6 "type": "git",
6 "type": "git",
src/Bridge.tsx+12−5
@@ -20,6 +20,9 @@ import light from '@tlon/indigo-light';
20
20
21import WithErrorBoundary from 'components/WithErrorBoundary';
21import WithErrorBoundary from 'components/WithErrorBoundary';
22import GlobalErrorBoundary from 'components/GlobalErrorBoundary';
22import GlobalErrorBoundary from 'components/GlobalErrorBoundary';
23+import { useBrowser } from 'lib/useBrowser';
24+import { BrowserWarning } from 'components/BrowserWarning';
25+
23import Modal from 'components/L2/Modal';
26import Modal from 'components/L2/Modal';
24import { useRollerStore } from 'store/rollerStore';
27import { useRollerStore } from 'store/rollerStore';
25import { Box } from '@tlon/indigo-react';
28import { Box } from '@tlon/indigo-react';
@@ -29,8 +32,8 @@ import { useRollerPoller } from 'lib/useRollerPoller';
29const INITIAL_NETWORK_TYPE = isGoerli
32const INITIAL_NETWORK_TYPE = isGoerli
30 ? NETWORK_TYPES.GOERLI
33 ? NETWORK_TYPES.GOERLI
31 : isDevelopment && !isMainnet
34 : isDevelopment && !isMainnet
32− ? NETWORK_TYPES.LOCAL
35+ ? NETWORK_TYPES.LOCAL
33− : NETWORK_TYPES.MAINNET;
36+ : NETWORK_TYPES.MAINNET;
34
37
35// NB(shrugs): modify these variables to change the default local state.
38// NB(shrugs): modify these variables to change the default local state.
36const SHOULD_STUB_LOCAL = import.meta.env.VITE_STUB_LOCAL === 'true';
39const SHOULD_STUB_LOCAL = import.meta.env.VITE_STUB_LOCAL === 'true';
@@ -38,9 +41,9 @@ const IS_STUBBED = isDevelopment && SHOULD_STUB_LOCAL;
38
41
39const INITIAL_WALLET = IS_STUBBED
42const INITIAL_WALLET = IS_STUBBED
40 ? walletFromMnemonic(
43 ? walletFromMnemonic(
41− import.meta.env.VITE_DEV_MNEMONIC as string,
44+ import.meta.env.VITE_DEV_MNEMONIC as string,
42− import.meta.env.VITE_HD_PATH as string
45+ import.meta.env.VITE_HD_PATH as string
43− )
46+ )
44 : undefined;
47 : undefined;
45const INITIAL_MNEMONIC = IS_STUBBED
48const INITIAL_MNEMONIC = IS_STUBBED
46 ? Just(import.meta.env.VITE_DEV_MNEMONIC)
49 ? Just(import.meta.env.VITE_DEV_MNEMONIC)
@@ -82,6 +85,9 @@ function Bridge() {
82 const initialRoutes = useInitialRoutes();
85 const initialRoutes = useInitialRoutes();
83 const { loading, modalText, setModalText } = useRollerStore();
86 const { loading, modalText, setModalText } = useRollerStore();
84
87
88+ const browser = useBrowser();
89+ const showBrowserWarning = browser && !browser.isChromium;
90+
85 // full reload if the user changes their selected network in Metamask
91 // full reload if the user changes their selected network in Metamask
86 //
92 //
87 useEffect(() => {
93 useEffect(() => {
@@ -103,6 +109,7 @@ function Bridge() {
103 initialWallet={INITIAL_WALLET}
109 initialWallet={INITIAL_WALLET}
104 initialMnemonic={INITIAL_MNEMONIC}
110 initialMnemonic={INITIAL_MNEMONIC}
105 initialPointCursor={INITIAL_POINT_CURSOR}>
111 initialPointCursor={INITIAL_POINT_CURSOR}>
112+ <BrowserWarning show={!!showBrowserWarning} />
106 <IndigoApp>
113 <IndigoApp>
107 <ThemeProvider theme={light}>
114 <ThemeProvider theme={light}>
108 <Router />
115 <Router />
src/components/BrowserWarning.tsxnew file+27
@@ -0,0 +1,27 @@
1+import React from 'react';
2+import { Box } from '@tlon/indigo-react';
3+import styled from 'styled-components';
4+
5+const WarningBox = styled(Box)`
6+ background: #fff4e5;
7+ color: #663c00;
8+ border: 1px solid #ffb74d;
9+ padding: 0.75rem 1rem;
10+ text-align: center;
11+ font-weight: 600;
12+ margin-bottom: 1rem;
13+`;
14+
15+interface Props {
16+ show: boolean;
17+}
18+export const BrowserWarning: React.FC<Props> = ({ show }) => {
19+ if (!show) return null;
20+ return (
21+ <WarningBox role="alert">
22+ For the best experience we recommend using a Chromium‑based browser
23+ (Chrome, Brave, etc.).
24+ </WarningBox>
25+ );
26+};
27+
src/components/InlineEthereumTransaction.jsx+4−7
@@ -13,7 +13,7 @@ import { toBN } from 'web3-utils';
13
13
14import { useExploreTxUrls } from 'lib/explorer';
14import { useExploreTxUrls } from 'lib/explorer';
15import { hexify } from 'lib/txn';
15import { hexify } from 'lib/txn';
16−import { safeFromWei, safeToWei } from 'lib/lib';
16+import { safeFromWei, gweiToWei } from 'lib/lib';
17
17
18import { composeValidator, buildCheckboxValidator } from 'form/validators';
18import { composeValidator, buildCheckboxValidator } from 'form/validators';
19import BridgeForm from 'form/BridgeForm';
19import BridgeForm from 'form/BridgeForm';
@@ -107,12 +107,9 @@ export default function InlineEthereumTransaction({
107 const maxCost = useMemo(
107 const maxCost = useMemo(
108 () =>
108 () =>
109 safeFromWei(
109 safeFromWei(
110− safeToWei(
111− toBN(gasLimit)
110+ toBN(gasLimit)
112− .mul(toBN(gasPrice.price))
111+ .mul(gweiToWei(gasPrice.price))
113− .mul(toBN(numTxs)),
112+ .mul(toBN(numTxs)),
114− 'gwei'
115− ),
116 'ether'
113 'ether'
117 ),
114 ),
118 [gasLimit, gasPrice, numTxs]
115 [gasLimit, gasPrice, numTxs]
src/components/L2/Dropdowns/FeeDropdown.tsx+4−1
@@ -43,7 +43,10 @@ export default function FeeDropdown({
43 );
43 );
44
44
45 const handleCustom = (e: React.ChangeEvent<HTMLInputElement>) => {
45 const handleCustom = (e: React.ChangeEvent<HTMLInputElement>) => {
46+ // Allow digits and a single decimal point
46− const cleanedValue = e.target.value.replace(/[^0-9]/g, '');
47+ const cleanedValue = e.target.value
48+ .replace(/[^0-9.]/g, '')
49+ .replace(/(\..*)\./g, '$1');
47 const cleanedNum = Number(cleanedValue);
50 const cleanedNum = Number(cleanedValue);
48 setCustom(cleanedValue);
51 setCustom(cleanedValue);
49 setGasPrice(defaultGasValues(cleanedNum).average);
52 setGasPrice(defaultGasValues(cleanedNum).average);
src/components/L2/Headers/L2PointHeader.tsx−3
@@ -92,9 +92,6 @@ const L2PointHeader = ({
92 <Row>
92 <Row>
93 <Row className="info">
93 <Row className="info">
94 {renderInviteButton()}
94 {renderInviteButton()}
95− <Box className="history" onClick={goToHistory}>
96− History
97− </Box>
98 {points && <InviteConverter points={points} />}
95 {points && <InviteConverter points={points} />}
99 </Row>
96 </Row>
100 </Row>
97 </Row>
src/lib/authToken.ts+4−3
@@ -1,7 +1,7 @@
1import { crypto } from 'bitcoinjs-lib';
1import { crypto } from 'bitcoinjs-lib';
2import { ecdsaSign } from 'secp256k1';
2import { ecdsaSign } from 'secp256k1';
3import Web3 from 'web3';
3import Web3 from 'web3';
4−import { hexToBytes } from 'web3-utils';
4+import { hexToBytes, utf8ToHex } from 'web3-utils';
5import { hashPersonalMessage } from '@ethereumjs/util';
5import { hashPersonalMessage } from '@ethereumjs/util';
6import { WALLET_TYPES } from './constants';
6import { WALLET_TYPES } from './constants';
7import BridgeWallet from './types/BridgeWallet';
7import BridgeWallet from './types/BridgeWallet';
@@ -76,10 +76,11 @@ const getMetamaskAuthToken = ({ address, web3 }: MetamaskAuthTokenArgs) => {
76 // but web3.eth.personal.sign hits it semi-reliably?
76 // but web3.eth.personal.sign hits it semi-reliably?
77 // no idea what's going on, we should figure it out,
77 // no idea what's going on, we should figure it out,
78 // but we apply this bandaid to hopefully stop the bleeding.
78 // but we apply this bandaid to hopefully stop the bleeding.
79+ //NOTE personal_sign expects hex-encoded data; signing the hex of
80+ // MESSAGE yields the same signature as the other wallet types
79 return window.ethereum.request({
81 return window.ethereum.request({
80 method: 'personal_sign',
82 method: 'personal_sign',
81− params: [MESSAGE, address],
83+ params: [utf8ToHex(MESSAGE), address],
82− from: address,
83 });
84 });
84 } else {
85 } else {
85 return web3.eth.personal.sign(MESSAGE, address, '');
86 return web3.eth.personal.sign(MESSAGE, address, '');
src/lib/getSuggestedGasPrice.js+61−71
@@ -5,13 +5,14 @@ import Web3 from 'web3';
5// ethgasstation returns values in floating point, one order of magitude
5// ethgasstation returns values in floating point, one order of magitude
6// more than gwei. see: https://docs.ethgasstation.info
6// more than gwei. see: https://docs.ethgasstation.info
7// we don't want to charge users more than the gas tank funds
7// we don't want to charge users more than the gas tank funds
8−const minGas = gas => Math.min(Math.ceil(gas), MAX_GAS_PRICE_GWEI);
8+const minGas = gas => Math.min(gas, MAX_GAS_PRICE_GWEI);
9−
10−const feeToInt = f => (f < 1 ? 1 : Math.round(f));
11
9
12const feeToWei = fee => Web3.utils.toHex(Web3.utils.toWei(String(fee), 'gwei'));
10const feeToWei = fee => Web3.utils.toHex(Web3.utils.toWei(String(fee), 'gwei'));
13
11
14−const weiToGwei = wei => Math.ceil(wei / 1_000_000_000);
12+// Gwei with milligwei precision, rounded up so we never underpay
13+const roundGwei = gwei => Math.ceil(gwei * 1000) / 1000;
14+
15+const weiToGwei = wei => roundGwei(wei / 1_000_000_000);
15
16
16const calculateMaxFee = (baseFee, maxPriorityFee) =>
17const calculateMaxFee = (baseFee, maxPriorityFee) =>
17 feeToWei(Math.round(2 * baseFee + maxPriorityFee));
18 feeToWei(Math.round(2 * baseFee + maxPriorityFee));
@@ -24,82 +25,75 @@ export const defaultGasValues = value => ({
24 price: value,
25 price: value,
25 wait: '1',
26 wait: '1',
26 maxFeePerGas: value,
27 maxFeePerGas: value,
27− maxPriorityFeePerGas: 1,
28+ maxPriorityFeePerGas: Math.min(1, value),
28 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
29 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
29 },
30 },
30 average: {
31 average: {
31 price: value,
32 price: value,
32 wait: '1',
33 wait: '1',
33 maxFeePerGas: value,
34 maxFeePerGas: value,
34− maxPriorityFeePerGas: 1,
35+ maxPriorityFeePerGas: Math.min(1, value),
35 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
36 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
36 },
37 },
37 low: {
38 low: {
38 price: value,
39 price: value,
39 wait: '1',
40 wait: '1',
40 maxFeePerGas: value,
41 maxFeePerGas: value,
41− maxPriorityFeePerGas: 1,
42+ maxPriorityFeePerGas: Math.min(1, value),
42 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
43 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
43 },
44 },
44});
45});
45
46
47+const median = values => {
48+ const sorted = [...values].sort((a, b) => a - b);
49+ const mid = Math.floor(sorted.length / 2);
50+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
51+};
52+
53+// Number of recent blocks to sample, and the priority fee percentiles
54+// used for the low, average and fast tiers respectively
55+const FEE_HISTORY_BLOCKS = 20;
56+const REWARD_PERCENTILES = [10, 50, 90];
57+// Floor for the priority fee, since zero tip transactions may never be mined
58+const MIN_PRIORITY_FEE_GWEI = 0.001;
59+
60+const tierFor = (baseFeeGwei, priorityFeeWei, wait) => {
61+ const maxPriorityFeePerGas = Math.max(
62+ weiToGwei(priorityFeeWei),
63+ MIN_PRIORITY_FEE_GWEI
64+ );
65+ const maxFeePerGas = roundGwei(baseFeeGwei + maxPriorityFeePerGas);
66+ return {
67+ price: minGas(maxFeePerGas),
68+ wait: formatWait(wait),
69+ maxFeePerGas,
70+ maxPriorityFeePerGas,
71+ suggestedBaseFeePerGas: baseFeeGwei,
72+ };
73+};
74+
75+// Estimates fees from the connected node via eth_feeHistory, in the same
76+// manner as wallets do. See: https://docs.alchemy.com/docs/how-to-build-a-gas-fee-estimator-using-eip-1559
46−const getGasForNetwork = async providerUrl => {
77+const getGasForNetwork = async web3 => {
47 try {
78 try {
48− const [feeResponse, baseFeeResponse] = await Promise.all([
79+ const history = await web3.eth.getFeeHistory(
49− fetch(
80+ FEE_HISTORY_BLOCKS,
50− 'https://beaconcha.in/api/v1/execution/gasnow',
81+ 'latest',
51− {
82+ REWARD_PERCENTILES
52− method: 'get',
53− cache: 'no-cache',
54− }
55− ),
83+ );
56− fetch(
84+
57− `${providerUrl}/v2/api` +
85+ // baseFeePerGas includes one extra entry: the base fee of the next block
58− '?module=gastracker' +
86+ const nextBaseFeeWei = Number(history.baseFeePerGas.at(-1));
59− '&action=gasoracle' +
87+ const baseFeeGwei = nextBaseFeeWei / 1_000_000_000;
60− '&apikey=CG52E4R96W56GIKUI4IJ8CH9EZIXPUW1W8' +
88+
61− '&chainid=1',
89+ const [low, average, fast] = REWARD_PERCENTILES.map((_, i) =>
62− {
90+ median(history.reward.map(rewards => Number(rewards[i])))
63− method: 'GET',
91+ );
64− cache: 'no-cache',
92+
65− }
66− ),
67− ]);
68−
69− const [feeJson, baseFeeJson] = await Promise.all([
70− feeResponse.json(),
71− baseFeeResponse.json(),
72− ]);
73−
74− const suggestedBaseFeePerGas = Number(baseFeeJson.result.suggestBaseFee);
75−
76− // Calculations derived from:
77− // https://www.blocknative.com/blog/eip-1559-fees
78 return {
93 return {
79− fast: {
94+ fast: tierFor(baseFeeGwei, fast, 15),
80− price: minGas(weiToGwei(feeJson.data.rapid)),
95+ average: tierFor(baseFeeGwei, average, 60),
81− wait: formatWait(15),
96+ low: tierFor(baseFeeGwei, low, 180),
82− maxFeePerGas: weiToGwei(feeJson.data.rapid),
83− maxPriorityFeePerGas: feeToInt(
84− weiToGwei(feeJson.data.rapid) - suggestedBaseFeePerGas
85− )
86− },
87− average: {
88− price: minGas(weiToGwei(feeJson.data.fast)),
89− wait: formatWait(60),
90− maxFeePerGas: weiToGwei(feeJson.data.fast),
91− maxPriorityFeePerGas: feeToInt(
92− weiToGwei(feeJson.data.fast) - suggestedBaseFeePerGas
93− )
94− },
95− low: {
96− price: minGas(weiToGwei(feeJson.data.slow)),
97− wait: formatWait(180),
98− maxFeePerGas: weiToGwei(feeJson.data.slow),
99− maxPriorityFeePerGas: feeToInt(
100− weiToGwei(feeJson.data.slow) - suggestedBaseFeePerGas
101− )
102− },
103 };
97 };
104 } catch (e) {
98 } catch (e) {
105 console.warn(e);
99 console.warn(e);
@@ -107,14 +101,10 @@ const getGasForNetwork = async providerUrl => {
107 }
101 }
108};
102};
109
103
110−export default async function getSuggestedGasPrice(networkType) {
104+export default async function getSuggestedGasPrice(networkType, web3) {
111− switch (networkType) {
112− case NETWORK_TYPES.GOERLI:
113− return getGasForNetwork('https://api-goerli.etherscan.io');
114− case NETWORK_TYPES.OFFLINE:
105+ if (networkType === NETWORK_TYPES.OFFLINE || !web3) {
115− return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
106+ return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
116− case NETWORK_TYPES.LOCAL:
117− default:
118− return getGasForNetwork('https://api.etherscan.io');
119 }
107 }
108+
109+ return getGasForNetwork(web3);
120}
110}
src/lib/lib.js+4
@@ -26,6 +26,9 @@ const safeToWei = (num, source) => {
26 }
26 }
27};
27};
28
28
29+// Converts a (possibly fractional) gwei amount to a wei BN
30+const gweiToWei = gwei => toBN(toWei(Number(gwei).toFixed(9), 'gwei'));
31+
29const compose = (...fs) =>
32const compose = (...fs) =>
30 fs.reduceRight(
33 fs.reduceRight(
31 (pF, nF) => (...args) => nF(pF(...args)),
34 (pF, nF) => (...args) => nF(pF(...args)),
@@ -73,6 +76,7 @@ const patpStringLength = byteLength =>
73export {
76export {
74 safeFromWei,
77 safeFromWei,
75 safeToWei,
78 safeToWei,
79+ gweiToWei,
76 compose,
80 compose,
77 allFalse,
81 allFalse,
78 isLast,
82 isLast,
src/lib/reticket.js+3−4
@@ -16,7 +16,7 @@ import {
16import getSuggestedGasPrice from './getSuggestedGasPrice';
16import getSuggestedGasPrice from './getSuggestedGasPrice';
17import { GAS_LIMITS, INITIAL_NETWORK_KEY_REVISION } from './constants';
17import { GAS_LIMITS, INITIAL_NETWORK_KEY_REVISION } from './constants';
18import { toBN } from 'web3-utils';
18import { toBN } from 'web3-utils';
19−import { safeToWei } from './lib';
19+import { gweiToWei } from './lib';
20import { isGalaxy, isPlanet } from './utils/point';
20import { isGalaxy, isPlanet } from './utils/point';
21
21
22export const TRANSACTION_PROGRESS = {
22export const TRANSACTION_PROGRESS = {
@@ -146,10 +146,9 @@ export async function reticketPointBetweenWallets({
146
146
147 progress(TRANSACTION_PROGRESS.SIGNING);
147 progress(TRANSACTION_PROGRESS.SIGNING);
148
148
149− const gasPriceData = (await getSuggestedGasPrice(networkType)).average;
149+ const gasPriceData = (await getSuggestedGasPrice(networkType, web3)).average;
150− const gasPriceGwei = gasPriceData.price.toFixed();
151 const chainId = await web3.eth.net.getId();
150 const chainId = await web3.eth.net.getId();
152− const gasPriceWeiBN = toBN(safeToWei(gasPriceGwei, 'gwei'));
151+ const gasPriceWeiBN = gweiToWei(gasPriceData.price);
153 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
152 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
154 const totalCost = txs.reduce(
153 const totalCost = txs.reduce(
155 (acc, tx) => acc.add(gasPriceWeiBN.mul(toBN(tx.gas))),
154 (acc, tx) => acc.add(gasPriceWeiBN.mul(toBN(tx.gas))),
src/lib/txn.ts+1−1
@@ -99,7 +99,7 @@ const signTransaction = async ({
99
99
100 // from user dropdown
100 // from user dropdown
101 const maxPriorityFeePerGas = toWei(
101 const maxPriorityFeePerGas = toWei(
102− String(Math.round(gasPriceData.maxPriorityFeePerGas)),
102+ Number(gasPriceData.maxPriorityFeePerGas).toFixed(9),
103 'gwei'
103 'gwei'
104 );
104 );
105
105
src/lib/useBrowser.tsnew file+32
@@ -0,0 +1,32 @@
1+import { useEffect, useState } from 'react';
2+
3+export function useBrowser() {
4+ const [browser, setBrowser] = useState<{
5+ name: 'chrome' | 'brave' | 'safari' | 'firefox' | 'edge' | 'other';
6+ isChromium: boolean;
7+ } | null>(null);
8+
9+ useEffect(() => {
10+ const ua = navigator.userAgent;
11+ const vendor = navigator.vendor ?? '';
12+
13+ const isChromium = !!(window as any).chrome;
14+ const isBrave = !!(navigator as any).brave;
15+ const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua);
16+ const isFirefox = /Firefox/.test(ua);
17+ const isEdge = /Edg/.test(ua);
18+ const isChrome = isChromium && !isBrave && !isEdge && /Chrome/.test(ua);
19+
20+ let name: typeof browser['name'] = 'other';
21+ if (isBrave) name = 'brave';
22+ else if (isChrome) name = 'chrome';
23+ else if (isSafari) name = 'safari';
24+ else if (isFirefox) name = 'firefox';
25+ else if (isEdge) name = 'edge';
26+
27+ setBrowser({ name, isChromium });
28+ }, []);
29+
30+ return browser;
31+}
32+
src/lib/useEthereumTransaction.js+9−13
@@ -23,7 +23,7 @@ import { ensureFundsFor } from 'lib/tank';
23import useDeepEqualReference from 'lib/useDeepEqualReference';
23import useDeepEqualReference from 'lib/useDeepEqualReference';
24import useGasPrice from 'lib/useGasPrice';
24import useGasPrice from 'lib/useGasPrice';
25import { timeout } from 'lib/timeout';
25import { timeout } from 'lib/timeout';
26−import { safeToWei, safeFromWei } from 'lib/lib';
26+import { safeFromWei, gweiToWei } from 'lib/lib';
27import { useWalletConnect } from './useWalletConnect';
27import { useWalletConnect } from './useWalletConnect';
28import { Transaction } from '@ethereumjs/tx';
28import { Transaction } from '@ethereumjs/tx';
29
29
@@ -181,10 +181,9 @@ export default function useEthereumTransaction(
181
181
182 const rawTxs = signedTransactions.map(stx => hexify(stx.serialize()));
182 const rawTxs = signedTransactions.map(stx => hexify(stx.serialize()));
183
183
184− const costGwei = toBN(gasLimit)
184+ const cost = toBN(gasLimit)
185− .mul(toBN(gasPrice.price))
185+ .mul(gweiToWei(gasPrice.price))
186 .mul(toBN(rawTxs.length));
186 .mul(toBN(rawTxs.length));
187− const cost = safeToWei(costGwei, 'gwei');
188 let usedTank = false;
187 let usedTank = false;
189 // if this ethereum transaction is being executed by a specific point
188 // if this ethereum transaction is being executed by a specific point
190 // see if we can use the tank
189 // see if we can use the tank
@@ -226,15 +225,12 @@ export default function useEthereumTransaction(
226
225
227 setFinalCost(
226 setFinalCost(
228 safeFromWei(
227 safeFromWei(
229− safeToWei(
230− receipts.reduce(
228+ receipts.reduce(
231− (acc, receipt) =>
229+ (acc, receipt) =>
232− toBN(receipt.gasUsed)
230+ toBN(receipt.gasUsed)
233− .mul(toBN(gasPrice.price))
231+ .mul(gweiToWei(gasPrice.price))
234− .add(acc),
232+ .add(acc),
235− toBN(0)
233+ toBN(0)
236− ),
237− 'gwei'
238 ),
234 ),
239 'ether'
235 'ether'
240 )
236 )
src/lib/useGasPrice.js+7−4
@@ -16,7 +16,7 @@ import { DEFAULT_GAS_PRICE_GWEI } from './constants';
16 */
16 */
17export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
17export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
18 const [mounted, setMounted] = useState(false);
18 const [mounted, setMounted] = useState(false);
19− const { networkType } = useNetwork();
19+ const { networkType, web3 } = useNetwork();
20 const [suggestedGasPrices, setSuggestedGasPrices] = useState(
20 const [suggestedGasPrices, setSuggestedGasPrices] = useState(
21 defaultGasValues(initialGasPrice)
21 defaultGasValues(initialGasPrice)
22 ); // fast, average, low. In gwei
22 ); // fast, average, low. In gwei
@@ -29,19 +29,22 @@ export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
29 return;
29 return;
30 }
30 }
31
31
32− const suggestedGasPrices = await getSuggestedGasPrices(networkType);
32+ const suggestedGasPrices = await getSuggestedGasPrices(
33+ networkType,
34+ web3.getOrElse(null)
35+ );
33 console.log(suggestedGasPrices);
36 console.log(suggestedGasPrices);
34
37
35 // TODO: BN
38 // TODO: BN
36 setSuggestedGasPrices(suggestedGasPrices);
39 setSuggestedGasPrices(suggestedGasPrices);
37 setGasPrice(suggestedGasPrices.average);
40 setGasPrice(suggestedGasPrices.average);
38− }, [mounted, networkType]);
41+ }, [mounted, networkType, web3]);
39
42
40 useEffect(() => {
43 useEffect(() => {
41 setMounted(true);
44 setMounted(true);
42 fetchPrices();
45 fetchPrices();
43 return () => setMounted(false);
46 return () => setMounted(false);
44− }, [mounted, networkType, fetchPrices]);
47+ }, [mounted, networkType, web3, fetchPrices]);
45
48
46 const resetGasPrice = useCallback(
49 const resetGasPrice = useCallback(
47 () => setGasPrice(suggestedGasPrices.average),
50 () => setGasPrice(suggestedGasPrices.average),
src/views/Activate/ActivateView.scss+31
@@ -1,3 +1,34 @@
1.activate-view {
1.activate-view {
2 min-height: 440px;
2 min-height: 440px;
3}
3}
4+
5+.activation-qualifier {
6+ /* Container appearance */
7+ margin-top: 1rem;
8+ // color: #000000; /* default text colour */
9+ padding: 1rem; /* some inner space */
10+ border-radius: 5px;
11+ background: rgba(0, 0, 0, 0.02);
12+ font-family: 'Inter';
13+ background: #fff4e5;
14+ color: #663c00;
15+ border: 1px solid #ffb74d;
16+}
17+
18+/* Each note inside the container */
19+.activation-qualifier .activation-note {
20+ margin-bottom: 1rem; /* space between notes */
21+}
22+
23+/* Remove the bottom margin from the last note */
24+.activation-qualifier .activation-note:last-child {
25+ margin-bottom: 0;
26+}
27+
28+/* Link styling – only for links that have the .linkout class */
29+.activation-qualifier a.linkout {
30+ color: #663c00;
31+ text-decoration: underline;
32+ font-weight: 600;
33+}
34+
src/views/Activate/ActivateView.tsx+16
@@ -60,6 +60,22 @@ export default function ActivateView({
60 </Box>
60 </Box>
61 </BodyPane>
61 </BodyPane>
62 </Window>
62 </Window>
63+ <div className="activation-qualifier">
64+ <div className="activation-note"> Planet activation codes will give you a self-custody <a className="linkout" href="https://docs.urbit.org/user-manual/id/layer-2-for-planets">Layer 2 Urbit 'planet'↗</a> and a Master Ticket wallet.
65+ </div>
66+ <div className="activation-note">
67+ This is a legacy feature not actively maintained in Bridge, if you run into issues we recommend you either: <br></br>
68+ <ol>
69+ <li>
70+ <a className="linkout" href="https://join.tlon.io/0v3.r87kb.fjpft.3k7b5.pbsr5.5em17">Claim a free Layer 2 planet from Tlon↗</a>
71+ </li>
72+ <br></br>
73+ <li>
74+ <a className="linkout" href="https://docs.urbit.org/user-manual/id/get-id#l1-planet-markets">Buy a Layer 1 identity on Ethereum↗</a>
75+ </li>
76+ </ol>
77+ </div>
78+ </div>
63 </Grid.Item>
79 </Grid.Item>
64 </Grid>
80 </Grid>
65 );
81 );
src/views/UrbitOS/NetworkKeys.tsx+16−6
@@ -279,8 +279,8 @@ export default function UrbitOSNetworkKeys({
279 const viewTitle = completed
279 const viewTitle = completed
280 ? 'Network Keys have been set'
280 ? 'Network Keys have been set'
281 : hasKeys
281 : hasKeys
282− ? 'Set Network Keys'
282+ ? 'Set Network Keys'
283− : 'Initiate Network Keys';
283+ : 'Initiate Network Keys';
284
284
285 const bootMessage = () => (
285 const bootMessage = () => (
286 <Text as="p" textAlign={'center'}>
286 <Text as="p" textAlign={'center'}>
@@ -311,9 +311,19 @@ export default function UrbitOSNetworkKeys({
311
311
312 const waitMessage = () => (
312 const waitMessage = () => (
313 <Text as="p" textAlign={'center'}>
313 <Text as="p" textAlign={'center'}>
314− <strong>Important:</strong> it may take minutes to hours for the
314+ <strong>Important:</strong> It may take minutes to hours for the
315+ keys to become usable as they propogate across the network.
315− keys to become usable. Please be patient, trying to use the new keys
316+ Please be patient, trying to use the new keys
316− during this period may result in an invalid keys error.
317+ during this period may result in an{' '}
318+ <
319+ Text as="code"
320+ mono
321+ bg={'washedGray'}
322+ padding={1}
323+ borderRadius={1}
324+ textAlign={'center'}>
325+ invalid keys</Text>
326+ {' '}error.
317 </Text>
327 </Text>
318 );
328 );
319
329
@@ -439,4 +449,4 @@ export default function UrbitOSNetworkKeys({
439 </BodyPane>
449 </BodyPane>
440 </Window>
450 </Window>
441 );
451 );
442−}
452+}