on ea25a45 help sign in
13 files changed+108−145
since splitsnapshots
unifiedsplit
package.json+1−1 src/components/InlineEthereumTransaction.jsx+7−4 src/components/L2/Dropdowns/FeeDropdown.tsx+1−4 src/components/L2/Headers/L2PointHeader.tsx+3 src/lib/authToken.ts+3−4 src/lib/getSuggestedGasPrice.js+71−61 src/lib/lib.js−4 src/lib/reticket.js+4−3 src/lib/txn.ts+1−1 src/lib/useEthereumTransaction.js+13−9 src/lib/useGasPrice.js+4−7 src/views/Activate/ActivateView.scss−31 src/views/Activate/ActivateView.tsx−16
package.json+1−1
@@ -1,6 +1,6 @@
11{
22 "name": "bridge",
3− "version": "3.3.1",
3+ "version": "3.3.0",
44 "description": "An application for interacting with Azimuth",
55 "repository": {
66 "type": "git",
src/components/InlineEthereumTransaction.jsx+7−4
@@ -13,7 +13,7 @@ import { toBN } from 'web3-utils';
1313
1414import { useExploreTxUrls } from 'lib/explorer';
1515import { hexify } from 'lib/txn';
16−import { safeFromWei, gweiToWei } from 'lib/lib';
16+import { safeFromWei, safeToWei } from 'lib/lib';
1717
1818import { composeValidator, buildCheckboxValidator } from 'form/validators';
1919import BridgeForm from 'form/BridgeForm';
@@ -107,9 +107,12 @@ export default function InlineEthereumTransaction({
107107 const maxCost = useMemo(
108108 () =>
109109 safeFromWei(
110− toBN(gasLimit)
111− .mul(gweiToWei(gasPrice.price))
112− .mul(toBN(numTxs)),
110+ safeToWei(
111+ toBN(gasLimit)
112+ .mul(toBN(gasPrice.price))
113+ .mul(toBN(numTxs)),
114+ 'gwei'
115+ ),
113116 'ether'
114117 ),
115118 [gasLimit, gasPrice, numTxs]
src/components/L2/Dropdowns/FeeDropdown.tsx+1−4
@@ -43,10 +43,7 @@ export default function FeeDropdown({
4343 );
4444
4545 const handleCustom = (e: React.ChangeEvent<HTMLInputElement>) => {
46− // Allow digits and a single decimal point
47− const cleanedValue = e.target.value
48− .replace(/[^0-9.]/g, '')
49− .replace(/(\..*)\./g, '$1');
46+ const cleanedValue = e.target.value.replace(/[^0-9]/g, '');
5047 const cleanedNum = Number(cleanedValue);
5148 setCustom(cleanedValue);
5249 setGasPrice(defaultGasValues(cleanedNum).average);
src/components/L2/Headers/L2PointHeader.tsx+3
@@ -92,6 +92,9 @@ const L2PointHeader = ({
9292 <Row>
9393 <Row className="info">
9494 {renderInviteButton()}
95+ <Box className="history" onClick={goToHistory}>
96+ History
97+ </Box>
9598 {points && <InviteConverter points={points} />}
9699 </Row>
97100 </Row>
src/lib/authToken.ts+3−4
@@ -1,7 +1,7 @@
11import { crypto } from 'bitcoinjs-lib';
22import { ecdsaSign } from 'secp256k1';
33import Web3 from 'web3';
4−import { hexToBytes, utf8ToHex } from 'web3-utils';
4+import { hexToBytes } from 'web3-utils';
55import { hashPersonalMessage } from '@ethereumjs/util';
66import { WALLET_TYPES } from './constants';
77import BridgeWallet from './types/BridgeWallet';
@@ -76,11 +76,10 @@ const getMetamaskAuthToken = ({ address, web3 }: MetamaskAuthTokenArgs) => {
7676 // but web3.eth.personal.sign hits it semi-reliably?
7777 // no idea what's going on, we should figure it out,
7878 // 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
8179 return window.ethereum.request({
8280 method: 'personal_sign',
83− params: [utf8ToHex(MESSAGE), address],
81+ params: [MESSAGE, address],
82+ from: address,
8483 });
8584 } else {
8685 return web3.eth.personal.sign(MESSAGE, address, '');
src/lib/getSuggestedGasPrice.js+71−61
@@ -5,14 +5,13 @@ import Web3 from 'web3';
55// ethgasstation returns values in floating point, one order of magitude
66// more than gwei. see: https://docs.ethgasstation.info
77// we don't want to charge users more than the gas tank funds
8−const minGas = gas => Math.min(gas, MAX_GAS_PRICE_GWEI);
8+const minGas = gas => Math.min(Math.ceil(gas), MAX_GAS_PRICE_GWEI);
99
10−const feeToWei = fee => Web3.utils.toHex(Web3.utils.toWei(String(fee), 'gwei'));
10+const feeToInt = f => (f < 1 ? 1 : Math.round(f));
1111
12−// Gwei with milligwei precision, rounded up so we never underpay
13−const roundGwei = gwei => Math.ceil(gwei * 1000) / 1000;
12+const feeToWei = fee => Web3.utils.toHex(Web3.utils.toWei(String(fee), 'gwei'));
1413
15−const weiToGwei = wei => roundGwei(wei / 1_000_000_000);
14+const weiToGwei = wei => Math.ceil(wei / 1_000_000_000);
1615
1716const calculateMaxFee = (baseFee, maxPriorityFee) =>
1817 feeToWei(Math.round(2 * baseFee + maxPriorityFee));
@@ -25,75 +24,82 @@ export const defaultGasValues = value => ({
2524 price: value,
2625 wait: '1',
2726 maxFeePerGas: value,
28− maxPriorityFeePerGas: Math.min(1, value),
27+ maxPriorityFeePerGas: 1,
2928 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
3029 },
3130 average: {
3231 price: value,
3332 wait: '1',
3433 maxFeePerGas: value,
35− maxPriorityFeePerGas: Math.min(1, value),
34+ maxPriorityFeePerGas: 1,
3635 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
3736 },
3837 low: {
3938 price: value,
4039 wait: '1',
4140 maxFeePerGas: value,
42− maxPriorityFeePerGas: Math.min(1, value),
41+ maxPriorityFeePerGas: 1,
4342 suggestedBaseFeePerGas: value > 2 ? value - 1 : 1,
4443 },
4544});
4645
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
77−const getGasForNetwork = async web3 => {
46+const getGasForNetwork = async providerUrl => {
7847 try {
79− const history = await web3.eth.getFeeHistory(
80− FEE_HISTORY_BLOCKS,
81− 'latest',
82− REWARD_PERCENTILES
83− );
84−
85− // baseFeePerGas includes one extra entry: the base fee of the next block
86− const nextBaseFeeWei = Number(history.baseFeePerGas.at(-1));
87− const baseFeeGwei = nextBaseFeeWei / 1_000_000_000;
88−
89− const [low, average, fast] = REWARD_PERCENTILES.map((_, i) =>
90− median(history.reward.map(rewards => Number(rewards[i])))
91− );
92−
48+ const [feeResponse, baseFeeResponse] = await Promise.all([
49+ fetch(
50+ 'https://beaconcha.in/api/v1/execution/gasnow',
51+ {
52+ method: 'get',
53+ cache: 'no-cache',
54+ }
55+ ),
56+ fetch(
57+ `${providerUrl}/v2/api` +
58+ '?module=gastracker' +
59+ '&action=gasoracle' +
60+ '&apikey=CG52E4R96W56GIKUI4IJ8CH9EZIXPUW1W8' +
61+ '&chainid=1',
62+ {
63+ method: 'GET',
64+ cache: 'no-cache',
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
9378 return {
94− fast: tierFor(baseFeeGwei, fast, 15),
95− average: tierFor(baseFeeGwei, average, 60),
96− low: tierFor(baseFeeGwei, low, 180),
79+ fast: {
80+ price: minGas(weiToGwei(feeJson.data.rapid)),
81+ wait: formatWait(15),
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+ },
97103 };
98104 } catch (e) {
99105 console.warn(e);
@@ -101,10 +107,14 @@ const getGasForNetwork = async web3 => {
101107 }
102108};
103109
104−export default async function getSuggestedGasPrice(networkType, web3) {
105− if (networkType === NETWORK_TYPES.OFFLINE || !web3) {
106− return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
110+export default async function getSuggestedGasPrice(networkType) {
111+ switch (networkType) {
112+ case NETWORK_TYPES.GOERLI:
113+ return getGasForNetwork('https://api-goerli.etherscan.io');
114+ case NETWORK_TYPES.OFFLINE:
115+ return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
116+ case NETWORK_TYPES.LOCAL:
117+ default:
118+ return getGasForNetwork('https://api.etherscan.io');
107119 }
108−
109− return getGasForNetwork(web3);
110120}
src/lib/lib.js−4
@@ -26,9 +26,6 @@ const safeToWei = (num, source) => {
2626 }
2727};
2828
29−// Converts a (possibly fractional) gwei amount to a wei BN
30−const gweiToWei = gwei => toBN(toWei(Number(gwei).toFixed(9), 'gwei'));
31−
3229const compose = (...fs) =>
3330 fs.reduceRight(
3431 (pF, nF) => (...args) => nF(pF(...args)),
@@ -76,7 +73,6 @@ const patpStringLength = byteLength =>
7673export {
7774 safeFromWei,
7875 safeToWei,
79− gweiToWei,
8076 compose,
8177 allFalse,
8278 isLast,
src/lib/reticket.js+4−3
@@ -16,7 +16,7 @@ import {
1616import getSuggestedGasPrice from './getSuggestedGasPrice';
1717import { GAS_LIMITS, INITIAL_NETWORK_KEY_REVISION } from './constants';
1818import { toBN } from 'web3-utils';
19−import { gweiToWei } from './lib';
19+import { safeToWei } from './lib';
2020import { isGalaxy, isPlanet } from './utils/point';
2121
2222export const TRANSACTION_PROGRESS = {
@@ -146,9 +146,10 @@ export async function reticketPointBetweenWallets({
146146
147147 progress(TRANSACTION_PROGRESS.SIGNING);
148148
149− const gasPriceData = (await getSuggestedGasPrice(networkType, web3)).average;
149+ const gasPriceData = (await getSuggestedGasPrice(networkType)).average;
150+ const gasPriceGwei = gasPriceData.price.toFixed();
150151 const chainId = await web3.eth.net.getId();
151− const gasPriceWeiBN = gweiToWei(gasPriceData.price);
152+ const gasPriceWeiBN = toBN(safeToWei(gasPriceGwei, 'gwei'));
152153 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
153154 const totalCost = txs.reduce(
154155 (acc, tx) => acc.add(gasPriceWeiBN.mul(toBN(tx.gas))),
src/lib/txn.ts+1−1
@@ -99,7 +99,7 @@ const signTransaction = async ({
9999
100100 // from user dropdown
101101 const maxPriorityFeePerGas = toWei(
102− Number(gasPriceData.maxPriorityFeePerGas).toFixed(9),
102+ String(Math.round(gasPriceData.maxPriorityFeePerGas)),
103103 'gwei'
104104 );
105105
src/lib/useEthereumTransaction.js+13−9
@@ -23,7 +23,7 @@ import { ensureFundsFor } from 'lib/tank';
2323import useDeepEqualReference from 'lib/useDeepEqualReference';
2424import useGasPrice from 'lib/useGasPrice';
2525import { timeout } from 'lib/timeout';
26−import { safeFromWei, gweiToWei } from 'lib/lib';
26+import { safeToWei, safeFromWei } from 'lib/lib';
2727import { useWalletConnect } from './useWalletConnect';
2828import { Transaction } from '@ethereumjs/tx';
2929
@@ -181,9 +181,10 @@ export default function useEthereumTransaction(
181181
182182 const rawTxs = signedTransactions.map(stx => hexify(stx.serialize()));
183183
184− const cost = toBN(gasLimit)
185− .mul(gweiToWei(gasPrice.price))
184+ const costGwei = toBN(gasLimit)
185+ .mul(toBN(gasPrice.price))
186186 .mul(toBN(rawTxs.length));
187+ const cost = safeToWei(costGwei, 'gwei');
187188 let usedTank = false;
188189 // if this ethereum transaction is being executed by a specific point
189190 // see if we can use the tank
@@ -225,12 +226,15 @@ export default function useEthereumTransaction(
225226
226227 setFinalCost(
227228 safeFromWei(
228− receipts.reduce(
229− (acc, receipt) =>
230− toBN(receipt.gasUsed)
231− .mul(gweiToWei(gasPrice.price))
232− .add(acc),
233− toBN(0)
229+ safeToWei(
230+ receipts.reduce(
231+ (acc, receipt) =>
232+ toBN(receipt.gasUsed)
233+ .mul(toBN(gasPrice.price))
234+ .add(acc),
235+ toBN(0)
236+ ),
237+ 'gwei'
234238 ),
235239 'ether'
236240 )
src/lib/useGasPrice.js+4−7
@@ -16,7 +16,7 @@ import { DEFAULT_GAS_PRICE_GWEI } from './constants';
1616 */
1717export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
1818 const [mounted, setMounted] = useState(false);
19− const { networkType, web3 } = useNetwork();
19+ const { networkType } = useNetwork();
2020 const [suggestedGasPrices, setSuggestedGasPrices] = useState(
2121 defaultGasValues(initialGasPrice)
2222 ); // fast, average, low. In gwei
@@ -29,22 +29,19 @@ export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
2929 return;
3030 }
3131
32− const suggestedGasPrices = await getSuggestedGasPrices(
33− networkType,
34− web3.getOrElse(null)
35− );
32+ const suggestedGasPrices = await getSuggestedGasPrices(networkType);
3633 console.log(suggestedGasPrices);
3734
3835 // TODO: BN
3936 setSuggestedGasPrices(suggestedGasPrices);
4037 setGasPrice(suggestedGasPrices.average);
41− }, [mounted, networkType, web3]);
38+ }, [mounted, networkType]);
4239
4340 useEffect(() => {
4441 setMounted(true);
4542 fetchPrices();
4643 return () => setMounted(false);
47− }, [mounted, networkType, web3, fetchPrices]);
44+ }, [mounted, networkType, fetchPrices]);
4845
4946 const resetGasPrice = useCallback(
5047 () => setGasPrice(suggestedGasPrices.average),
src/views/Activate/ActivateView.scss−31
@@ -1,34 +1,3 @@
11.activate-view {
22 min-height: 440px;
33}
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,22 +60,6 @@ export default function ActivateView({
6060 </Box>
6161 </BodyPane>
6262 </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>
7963 </Grid.Item>
8064 </Grid>
8165 );