Skip to content

Commit

Permalink
Activities fixes (#1419)
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-schrammel authored Apr 8, 2024
1 parent 865945f commit af3fa7c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 72 deletions.
1 change: 1 addition & 0 deletions src/core/providers/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChainId } from '../types/chains';

export const proxyRpcEndpoint = (endpoint: string, chainId: ChainId) => {
if (
endpoint &&
endpoint !== 'http://127.0.0.1:8545' &&
endpoint !== 'http://localhost:8545' &&
!endpoint.includes('http://10.') &&
Expand Down
7 changes: 1 addition & 6 deletions src/core/resources/transactions/consolidatedTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ async function parseConsolidatedTransactions(
currency: SupportedCurrencyKey,
) {
const data = message?.payload?.transactions || [];
const parsedTransactionPromises = data
return data
.map((tx) =>
parseTransaction({
tx,
Expand All @@ -145,11 +145,6 @@ async function parseConsolidatedTransactions(
}),
)
.filter(Boolean);

const parsedConsolidatedTransactions = (
await Promise.all(parsedTransactionPromises)
).flat();
return parsedConsolidatedTransactions;
}

// ///////////////////////////////////////////////
Expand Down
31 changes: 28 additions & 3 deletions src/core/resources/transactions/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { formatUnits } from '@ethersproject/units';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Hash, getProvider } from '@wagmi/core';
import { Address } from 'wagmi';
Expand All @@ -10,7 +11,11 @@ import {
consolidatedTransactionsQueryFunction,
consolidatedTransactionsQueryKey,
} from '~/core/resources/transactions/consolidatedTransactions';
import { useCurrentAddressStore, useCurrentCurrencyStore } from '~/core/state';
import {
pendingTransactionsStore,
useCurrentAddressStore,
useCurrentCurrencyStore,
} from '~/core/state';
import { useTestnetModeStore } from '~/core/state/currentSettings/testnetMode';
import { ChainId } from '~/core/types/chains';
import {
Expand All @@ -28,6 +33,14 @@ type ConsolidatedTransactionsResult = QueryFunctionResult<
typeof consolidatedTransactionsQueryFunction
>;

const searchInLocalPendingTransactions = (userAddress: Address, hash: Hash) => {
const { pendingTransactions } = pendingTransactionsStore.getState();
const localPendingTx = pendingTransactions[userAddress]?.find(
(tx) => tx.hash === hash,
);
return localPendingTx;
};

export const fetchTransaction = async ({
hash,
address,
Expand All @@ -48,16 +61,25 @@ export const fetchTransaction = async ({
});
const tx = response.data.payload.transaction;
if (response.data.meta.status === 'pending') {
const localPendingTx = searchInLocalPendingTransactions(address, hash);
if (localPendingTx) return localPendingTx;

const providerTx = await getCustomChainTransaction({ chainId, hash });
return providerTx;
}
const parsedTx = parseTransaction({ tx, currency, chainId });
if (!parsedTx) throw new Error('Failed to parse transaction');
return parsedTx;
} catch (e) {
// if it's a pending tx BE may be in another mempool and it will return 404,
// which throws and gets caught here, so we check if we got it in localstorage
const localPendingTx = searchInLocalPendingTransactions(address, hash);
if (localPendingTx) return localPendingTx;

logger.error(new RainbowError('fetchTransaction: '), {
message: (e as Error)?.message,
});
throw e; // log & rethrow
}
};

Expand Down Expand Up @@ -133,6 +155,9 @@ const getCustomChainTransaction = async ({
? await provider.getBlock(transaction?.blockHash)
: undefined;

const decimals = 18; // assuming every chain uses 18 decimals
const value = formatUnits(transaction.value, decimals);

const parsedTransaction = transaction.blockNumber
? ({
status: 'confirmed',
Expand All @@ -146,7 +171,7 @@ const getCustomChainTransaction = async ({
from: transaction.from as Address,
to: transaction.to as Address,
data: transaction.data,
value: transaction.value.toString(),
value,
type: 'send',
title: i18n.t('transactions.send.confirmed'),
baseFee: block?.baseFeePerGas?.toString(),
Expand All @@ -162,7 +187,7 @@ const getCustomChainTransaction = async ({
from: transaction.from as Address,
to: transaction.to as Address,
data: transaction.data,
value: transaction.value.toString(),
value,
type: 'send',
title: i18n.t('transactions.send.pending'),
} satisfies PendingTransaction);
Expand Down
7 changes: 1 addition & 6 deletions src/core/resources/transactions/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async function parseTransactions(
currency: SupportedCurrencyKey,
) {
const data = message?.payload?.transactions || [];
const parsedTransactionPromises = data
return data
.map((tx) =>
parseTransaction({
tx,
Expand All @@ -114,11 +114,6 @@ async function parseTransactions(
}),
)
.filter(Boolean);

const parsedTransactions = (
await Promise.all(parsedTransactionPromises)
).flat();
return parsedTransactions;
}

// ///////////////////////////////////////////////
Expand Down
6 changes: 4 additions & 2 deletions src/core/utils/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,10 @@ export function parseTransaction({

if (
!type ||
(transactionTypeShouldHaveChanges(type) && changes.length === 0) ||
!tx.address_from
!tx.address_from ||
(status !== 'failed' && // failed txs won't have changes
transactionTypeShouldHaveChanges(type) &&
changes.length === 0)
)
return; // filters some spam or weird api responses

Expand Down
24 changes: 10 additions & 14 deletions src/entries/popup/pages/home/Activity/ActivityContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { i18n } from '~/core/languages';
import { shortcuts } from '~/core/references/shortcuts';
import { useCurrentHomeSheetStore } from '~/core/state/currentHomeSheet';
import { useSelectedTransactionStore } from '~/core/state/selectedTransaction';
import { ChainId } from '~/core/types/chains';
import { RainbowTransaction } from '~/core/types/transactions';
import { truncateAddress } from '~/core/utils/address';
import { copy } from '~/core/utils/copy';
Expand Down Expand Up @@ -45,10 +44,7 @@ export function ActivityContextMenu({
});
};

const viewOnExplorer = () => {
const explorer = getTransactionBlockExplorer(transaction);
goToNewTab({ url: explorer?.url });
};
const explorer = getTransactionBlockExplorer(transaction);

const onSpeedUp = () => {
setCurrentHomeSheet('speedUp');
Expand Down Expand Up @@ -115,15 +111,15 @@ export function ActivityContextMenu({
</>
)}

<ContextMenuItem
symbolLeft="binoculars.fill"
onSelect={viewOnExplorer}
shortcut={shortcuts.activity.VIEW_TRANSACTION.display}
>
{transaction?.chainId === ChainId.mainnet
? i18n.t('speed_up_and_cancel.view_on_etherscan')
: i18n.t('speed_up_and_cancel.view_on_explorer')}
</ContextMenuItem>
{explorer && (
<ContextMenuItem
symbolLeft="binoculars.fill"
onSelect={() => goToNewTab({ url: explorer.url })}
shortcut={shortcuts.activity.VIEW_TRANSACTION.display}
>
{i18n.t('view_on_explorer', { explorer: explorer.name })}
</ContextMenuItem>
)}

<Box testId="activity-context-copy-tx-hash">
<ContextMenuItem
Expand Down
111 changes: 70 additions & 41 deletions src/entries/popup/pages/home/Activity/ActivityDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { formatEther, formatUnits } from '@ethersproject/units';
import { BigNumber } from '@ethersproject/bignumber';
import { formatUnits } from '@ethersproject/units';
import { motion } from 'framer-motion';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
Expand All @@ -11,7 +12,7 @@ import { useCurrentHomeSheetStore } from '~/core/state/currentHomeSheet';
import { ChainId, ChainNameDisplay } from '~/core/types/chains';
import { RainbowTransaction, TxHash } from '~/core/types/transactions';
import { truncateAddress } from '~/core/utils/address';
import { getChain } from '~/core/utils/chains';
import { findRainbowChainForChainId } from '~/core/utils/chains';
import { copy } from '~/core/utils/copy';
import { formatDate } from '~/core/utils/formatDate';
import { formatCurrency, formatNumber } from '~/core/utils/formatNumber';
Expand Down Expand Up @@ -47,7 +48,6 @@ import {
DropdownMenuTrigger,
} from '~/entries/popup/components/DropdownMenu/DropdownMenu';
import { Navbar } from '~/entries/popup/components/Navbar/Navbar';
import { useRainbowChains } from '~/entries/popup/hooks/useRainbowChains';
import { useRainbowNavigate } from '~/entries/popup/hooks/useRainbowNavigate';
import { useWallets } from '~/entries/popup/hooks/useWallets';
import { ROUTES } from '~/entries/popup/urls';
Expand Down Expand Up @@ -150,32 +150,55 @@ function ConfirmationData({

const InfoValueSkeleton = () => <Skeleton width="50px" height="12px" />;

const formatFee = (transaction: RainbowTransaction) => {
if (
transaction.native !== undefined &&
transaction.native.fee !== undefined
) {
// if the fee is less than $0.01, the provider returns 0 so we display it as <$0.01
const feeInNative =
+transaction.native.fee <= 0.01 ? 0.01 : transaction.native.fee;
return `${+feeInNative <= 0.01 ? '<' : ''}${formatCurrency(feeInNative)}`;
}

const nativeCurrencySymbol = findRainbowChainForChainId(transaction.chainId)
?.nativeCurrency.symbol;

if (!transaction.fee || !nativeCurrencySymbol) return;

return `${formatNumber(transaction.fee)} ${nativeCurrencySymbol}`;
};
function FeeData({ transaction: tx }: { transaction: RainbowTransaction }) {
const { native, feeType } = tx;
const { feeType } = tx;

// if baseFee is undefined (like in pending txs or custom networks the api wont have data about it)
// so we try to calculate with the data we may have locally
const baseFee =
tx.baseFee ||
(tx.maxFeePerGas &&
tx.maxPriorityFeePerGas &&
BigNumber.from(tx.maxFeePerGas).sub(tx.maxPriorityFeePerGas).toString());

const maxPriorityFeePerGas =
tx.maxPriorityFeePerGas && formatUnits(tx.maxPriorityFeePerGas, 'gwei');
const maxFeePerGas = tx.maxFeePerGas && formatUnits(tx.maxFeePerGas, 'gwei');
const baseFee = tx.baseFee && formatUnits(tx.baseFee, 'gwei');
const fee = formatFee(tx);

const gasPrice = tx.gasPrice && formatUnits(tx.gasPrice, 'gwei');
if ((!baseFee || !tx.maxPriorityFeePerGas) && !tx.gasPrice) return null;

return (
<>
{native?.fee && (
{fee && (
<InfoRow
symbol="fuelpump.fill"
label={i18n.t('activity_details.fee')}
value={formatCurrency(native.fee)}
value={fee}
/>
)}
{feeType === 'legacy' ? (
<>
{gasPrice && (
{tx.gasPrice && (
<InfoRow
symbol="barometer"
label={i18n.t('activity_details.gas_price')}
value={`${formatNumber(gasPrice)} Gwei`}
value={`${formatNumber(formatUnits(tx.gasPrice, 'gwei'))} Gwei`}
/>
)}
</>
Expand All @@ -185,15 +208,8 @@ function FeeData({ transaction: tx }: { transaction: RainbowTransaction }) {
symbol="barometer"
label={i18n.t('activity_details.base_fee')}
value={
baseFee ? `${formatNumber(baseFee)} Gwei` : <InfoValueSkeleton />
}
/>
<InfoRow
symbol="barometer"
label={i18n.t('activity_details.max_base_fee')}
value={
maxFeePerGas ? (
`${formatNumber(maxFeePerGas)} Gwei`
baseFee ? (
`${formatNumber(formatUnits(baseFee, 'gwei'))} Gwei`
) : (
<InfoValueSkeleton />
)
Expand All @@ -203,8 +219,10 @@ function FeeData({ transaction: tx }: { transaction: RainbowTransaction }) {
symbol="barometer"
label={i18n.t('activity_details.max_priority_fee')}
value={
maxPriorityFeePerGas ? (
`${formatNumber(maxPriorityFeePerGas)} Gwei`
tx.maxPriorityFeePerGas ? (
`${formatNumber(
formatUnits(tx.maxPriorityFeePerGas, 'gwei'),
)} Gwei`
) : (
<InfoValueSkeleton />
)
Expand All @@ -216,25 +234,37 @@ function FeeData({ transaction: tx }: { transaction: RainbowTransaction }) {
);
}

const formatValue = (transaction: RainbowTransaction) => {
const formattedValueInNative =
transaction.native &&
transaction.native.value &&
Number(transaction.native.value) > 0 &&
formatCurrency(transaction.native.value);

if (formattedValueInNative) return formattedValueInNative;

const nativeCurrencySymbol = findRainbowChainForChainId(transaction.chainId)
?.nativeCurrency.symbol;

if (!nativeCurrencySymbol) return;

const formattedValue =
Number(transaction.value) > 0 &&
`${formatNumber(transaction.value)} ${nativeCurrencySymbol}`;

return formattedValue;
};
function NetworkData({ transaction: tx }: { transaction: RainbowTransaction }) {
const { nonce, native, value } = tx;
const { rainbowChains } = useRainbowChains();
const chain = getChain({ chainId: tx.chainId });
const chain = findRainbowChainForChainId(tx.chainId);
const value = formatValue(tx);

return (
<Stack space="24px">
{native?.value && +native?.value > 0 && (
<InfoRow
symbol="dollarsign.square"
label={i18n.t('activity_details.value')}
value={formatCurrency(native.value)}
/>
)}
{!(native?.value && +native?.value) && value && (
{value && (
<InfoRow
symbol="dollarsign.square"
label={i18n.t('activity_details.value')}
value={`${formatEther(+value)} ${chain.nativeCurrency.symbol}`}
value={value}
/>
)}
<InfoRow
Expand All @@ -243,17 +273,16 @@ function NetworkData({ transaction: tx }: { transaction: RainbowTransaction }) {
value={
<Inline alignVertical="center" space="4px">
<ChainBadge chainId={tx.chainId} size={12} />
{ChainNameDisplay[tx.chainId] ||
rainbowChains.find((chain) => chain.id === tx.chainId)?.name}
{ChainNameDisplay[tx.chainId] || chain?.name}
</Inline>
}
/>
{tx.status != 'pending' && <FeeData transaction={tx} />}
{nonce >= 0 && (
<FeeData transaction={tx} />
{tx.nonce >= 0 && (
<InfoRow
symbol="number"
label={i18n.t('activity_details.nonce')}
value={nonce}
value={tx.nonce}
/>
)}
</Stack>
Expand Down
1 change: 1 addition & 0 deletions static/json/languages/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1742,6 +1742,7 @@
"unverified": "Unverified",
"skip": "Skip",
"done": "Done",
"view_on_explorer": "View on %{explorer}",
"watch_asset": {
"chain_id": "Chain ID",
"symbol": "Symbol",
Expand Down

0 comments on commit af3fa7c

Please sign in to comment.