diff --git a/packages/extension/src/background/transactions/sources/onchain.ts b/packages/extension/src/background/transactions/sources/onchain.ts index 9cab2fb2a..ff2e72d4a 100644 --- a/packages/extension/src/background/transactions/sources/onchain.ts +++ b/packages/extension/src/background/transactions/sources/onchain.ts @@ -1,15 +1,22 @@ -import { ExplorerProvider } from "@alephium/web3" +import { explorer, ExplorerProvider } from "@alephium/web3" import { getNetwork } from "../../../shared/network" -import { compareTransactions, getInFlightTransactions, Transaction } from "../../../shared/transactions" +import { compareTransactions, getInFlightTransactions, LatestTransaction, Transaction } from "../../../shared/transactions" import { transactionsStore } from "../../../shared/transactions/store" +import { mapAlephiumTransactionToTransaction } from "../../../shared/transactions/transformers" import { getTransactionsStatusUpdate } from "../determineUpdates" import { groupBy, forEach } from "lodash" -interface TransactionUpdates { +interface TransactionUpdates { toBeRemoved: Transaction[] - toBeStored: Transaction[] + toBeStored: T[] } +// See https://github.com/alephium/alephium-frontend/issues/1367 +export const isConfirmedTx = ( + tx: explorer.TransactionLike, +): tx is explorer.AcceptedTransaction => + "blockHash" in tx && !tx.inputs?.some((input) => input.txHashRef === undefined) + export async function getTransactionsUpdate(transactionsToCheck: Transaction[]) { // as this function tends to run into 429 errors, we'll simply keep the old status when it fails @@ -51,19 +58,19 @@ export async function getTransactionsUpdate(transactionsToCheck: Transaction[]) export function getUpdatesFromLatestTransactions( existingTransactions: Transaction[], - latestTransactions: Transaction[] -): TransactionUpdates { + latestTransactions: LatestTransaction[] +): TransactionUpdates { const pendingTransactions = getInFlightTransactions(existingTransactions) // Remove all pending tx that are part of the latest txs const toBeRemoved = pendingTransactions.filter((pendingTx) => { - !!latestTransactions.find((tx) => compareTransactions(pendingTx, tx)) + return !!latestTransactions.find((tx) => compareTransactions(pendingTx, tx)) }) // Store all latest tx that are not part of the existing txs, except that // they are part of the pending txs const toBeStored = latestTransactions.filter((latestTx) => { - !existingTransactions.find((tx) => compareTransactions(latestTx, tx)) || + return !existingTransactions.find((tx) => compareTransactions(latestTx, tx)) || !!pendingTransactions.find((tx) => compareTransactions(latestTx, tx)) }) @@ -86,13 +93,31 @@ export function getPruneTransactions( return { toBeRemoved: transactionsToPrune, toBeStored: [] } } -export async function storeTransactionUpdates(transactionUpdates: TransactionUpdates) { +export async function storeTransactionUpdates(transactionUpdates: TransactionUpdates) { if (transactionUpdates.toBeRemoved.length > 0) { await transactionsStore.remove((tx) => transactionUpdates.toBeRemoved.some((toBeRemovedTx) => tx.hash === toBeRemovedTx.hash)) } if (transactionUpdates.toBeStored.length > 0) { - await transactionsStore.push(transactionUpdates.toBeStored) + const transactionsToStore = await Promise.all(transactionUpdates.toBeStored.map(async (transaction) => { + if ("status" in transaction) { + return transaction + } + + const network = await getNetwork(transaction.account.networkId) + const explorerProvider = new ExplorerProvider(network.explorerApiUrl) + const fullTransaction = await explorerProvider.transactions.getTransactionsTransactionHash(transaction.hash) + if (!isConfirmedTx(fullTransaction)) { + throw new Error(`Expected confirmed transaction for ${transaction.hash}`) + } + return mapAlephiumTransactionToTransaction( + fullTransaction, + transaction.account, + transaction.meta, + ) + })) + + await transactionsStore.push(transactionsToStore) } } diff --git a/packages/extension/src/background/transactions/sources/voyager.ts b/packages/extension/src/background/transactions/sources/voyager.ts index 3983f4aca..c42cebbd4 100644 --- a/packages/extension/src/background/transactions/sources/voyager.ts +++ b/packages/extension/src/background/transactions/sources/voyager.ts @@ -1,7 +1,7 @@ import join from "url-join" import { Network } from "../../../shared/network" -import { Transaction, getTransactionsPerAccount } from "../../../shared/transactions" +import { LatestTransaction, Transaction, getTransactionsPerAccount } from "../../../shared/transactions" import { WalletAccount } from "../../../shared/wallet.model" import { fetchWithTimeout } from "../../utils/fetchWithTimeout" @@ -35,7 +35,7 @@ export const fetchVoyagerTransactions = async ( export async function getLatestTransactions( accountsToPopulate: WalletAccount[], metadataTransactions: Transaction[], -) { +): Promise { const transactionsPerAccount = await getTransactionsPerAccount(accountsToPopulate, metadataTransactions) return Array.from(transactionsPerAccount.values()).flat() } diff --git a/packages/extension/src/shared/transactions/index.ts b/packages/extension/src/shared/transactions/index.ts index 12f2a045a..90a21299f 100644 --- a/packages/extension/src/shared/transactions/index.ts +++ b/packages/extension/src/shared/transactions/index.ts @@ -2,9 +2,9 @@ import { ALPH_TOKEN_ID, DEFAULT_GAS_PRICE, DUST_AMOUNT, ExplorerProvider, Groupl import { lowerCase, upperFirst } from "lodash-es" import { Call } from "starknet" import { ReviewTransactionResult, TransactionParams } from "../actionQueue/types" -import { WalletAccount } from "../wallet.model" import { AlephiumExplorerTransaction } from "../explorer/type" -import { grouplessTxResultToReviewTransactionResult, mapAlephiumTransactionToTransaction, signedChainedTxResultToReviewTransactionResult, transactionParamsToSignChainedTxParams } from "./transformers" +import { WalletAccount } from "../wallet.model" +import { grouplessTxResultToReviewTransactionResult, signedChainedTxResultToReviewTransactionResult, transactionParamsToSignChainedTxParams } from "./transformers" import { getNetwork } from "../network" import { BaseTokenWithBalance } from "../token/type" import { BigNumber } from "ethers" @@ -49,6 +49,10 @@ export interface TransactionRequest extends TransactionBase { meta?: TransactionMeta } +export interface LatestTransaction extends TransactionRequest { + timestamp: number +} + export interface Transaction extends TransactionRequest { status: Status failureReason?: { code: string; error_message: string } @@ -94,9 +98,9 @@ export function transactionNamesToTitle( export async function getTransactionsPerAccount( accountsToPopulate: WalletAccount[], metadataTransactions: Transaction[], -): Promise> { +): Promise> { const getTransactions = buildGetTransactionsFn(metadataTransactions) - const transactionsPerAccount = new Map() + const transactionsPerAccount = new Map() await Promise.all( accountsToPopulate.map(async (account) => { const transactions = await getTransactions(account) @@ -109,23 +113,31 @@ export async function getTransactionsPerAccount( // Fetch 1 tx function buildGetTransactionsFn(metadataTransactions: Transaction[]) { - return async (account: WalletAccount) => { - const limit = 1 + return async (account: WalletAccount): Promise => { const network = await getNetwork(account.networkId) const explorerProvider = new ExplorerProvider(network.explorerApiUrl) - const transactions = await explorerProvider.addresses.getAddressesAddressTransactions(account.address, { page: 1, limit }) - return transactions.map((transaction) => - mapAlephiumTransactionToTransaction( - transaction, + try { + const latestTransaction = await explorerProvider.addresses.getAddressesAddressLatestTransaction(account.address) + const meta = metadataTransactions.find((tx) => + compareTransactions(tx, { + hash: latestTransaction.hash, + account: { networkId: account.networkId }, + }), + )?.meta + + return [{ + hash: latestTransaction.hash, account, - metadataTransactions.find((tx) => - compareTransactions(tx, { - hash: transaction.hash, - account: { networkId: account.networkId }, - }), - )?.meta, - ), - ) + meta, + timestamp: latestTransaction.timestamp, + }] + } catch (error) { + if (error instanceof Error && error.message.includes("Status code: 404")) { + return [] + } + + throw error + } } }