diff --git a/src/core/providers/proxy.ts b/src/core/providers/proxy.ts
index a3f2243631..7c4de22309 100644
--- a/src/core/providers/proxy.ts
+++ b/src/core/providers/proxy.ts
@@ -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.') &&
diff --git a/src/core/resources/transactions/consolidatedTransactions.ts b/src/core/resources/transactions/consolidatedTransactions.ts
index 5bb8b8b5a6..b124815a21 100644
--- a/src/core/resources/transactions/consolidatedTransactions.ts
+++ b/src/core/resources/transactions/consolidatedTransactions.ts
@@ -136,7 +136,7 @@ async function parseConsolidatedTransactions(
currency: SupportedCurrencyKey,
) {
const data = message?.payload?.transactions || [];
- const parsedTransactionPromises = data
+ return data
.map((tx) =>
parseTransaction({
tx,
@@ -145,11 +145,6 @@ async function parseConsolidatedTransactions(
}),
)
.filter(Boolean);
-
- const parsedConsolidatedTransactions = (
- await Promise.all(parsedTransactionPromises)
- ).flat();
- return parsedConsolidatedTransactions;
}
// ///////////////////////////////////////////////
diff --git a/src/core/resources/transactions/transaction.ts b/src/core/resources/transactions/transaction.ts
index 020c02a3ff..d183dd0ac2 100644
--- a/src/core/resources/transactions/transaction.ts
+++ b/src/core/resources/transactions/transaction.ts
@@ -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';
@@ -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 {
@@ -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,
@@ -48,6 +61,9 @@ 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;
}
@@ -55,9 +71,15 @@ export const fetchTransaction = async ({
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
}
};
@@ -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',
@@ -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(),
@@ -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);
diff --git a/src/core/resources/transactions/transactions.ts b/src/core/resources/transactions/transactions.ts
index 8aa44285d1..512a6f999b 100644
--- a/src/core/resources/transactions/transactions.ts
+++ b/src/core/resources/transactions/transactions.ts
@@ -103,7 +103,7 @@ async function parseTransactions(
currency: SupportedCurrencyKey,
) {
const data = message?.payload?.transactions || [];
- const parsedTransactionPromises = data
+ return data
.map((tx) =>
parseTransaction({
tx,
@@ -114,11 +114,6 @@ async function parseTransactions(
}),
)
.filter(Boolean);
-
- const parsedTransactions = (
- await Promise.all(parsedTransactionPromises)
- ).flat();
- return parsedTransactions;
}
// ///////////////////////////////////////////////
diff --git a/src/core/utils/transactions.ts b/src/core/utils/transactions.ts
index 80c0e81aa3..a957f20817 100644
--- a/src/core/utils/transactions.ts
+++ b/src/core/utils/transactions.ts
@@ -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
diff --git a/src/entries/popup/pages/home/Activity/ActivityContextMenu.tsx b/src/entries/popup/pages/home/Activity/ActivityContextMenu.tsx
index ee8f06c540..099e5a18cf 100644
--- a/src/entries/popup/pages/home/Activity/ActivityContextMenu.tsx
+++ b/src/entries/popup/pages/home/Activity/ActivityContextMenu.tsx
@@ -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';
@@ -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');
@@ -115,15 +111,15 @@ export function ActivityContextMenu({
>
)}
-
- {transaction?.chainId === ChainId.mainnet
- ? i18n.t('speed_up_and_cancel.view_on_etherscan')
- : i18n.t('speed_up_and_cancel.view_on_explorer')}
-
+ {explorer && (
+ goToNewTab({ url: explorer.url })}
+ shortcut={shortcuts.activity.VIEW_TRANSACTION.display}
+ >
+ {i18n.t('view_on_explorer', { explorer: explorer.name })}
+
+ )}
;
+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 && (
)}
{feeType === 'legacy' ? (
<>
- {gasPrice && (
+ {tx.gasPrice && (
)}
>
@@ -185,15 +208,8 @@ function FeeData({ transaction: tx }: { transaction: RainbowTransaction }) {
symbol="barometer"
label={i18n.t('activity_details.base_fee')}
value={
- baseFee ? `${formatNumber(baseFee)} Gwei` :
- }
- />
-
)
@@ -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`
) : (
)
@@ -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 (
- {native?.value && +native?.value > 0 && (
-
- )}
- {!(native?.value && +native?.value) && value && (
+ {value && (
)}
- {ChainNameDisplay[tx.chainId] ||
- rainbowChains.find((chain) => chain.id === tx.chainId)?.name}
+ {ChainNameDisplay[tx.chainId] || chain?.name}
}
/>
- {tx.status != 'pending' && }
- {nonce >= 0 && (
+
+ {tx.nonce >= 0 && (
)}
diff --git a/static/json/languages/en_US.json b/static/json/languages/en_US.json
index b8cb9dd576..75a29fc5a9 100644
--- a/static/json/languages/en_US.json
+++ b/static/json/languages/en_US.json
@@ -1742,6 +1742,7 @@
"unverified": "Unverified",
"skip": "Skip",
"done": "Done",
+ "view_on_explorer": "View on %{explorer}",
"watch_asset": {
"chain_id": "Chain ID",
"symbol": "Symbol",