Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions packages/extension/src/background/transactions/sources/onchain.ts
Original file line number Diff line number Diff line change
@@ -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<T = Transaction> {
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
Expand Down Expand Up @@ -51,19 +58,19 @@ export async function getTransactionsUpdate(transactionsToCheck: Transaction[])

export function getUpdatesFromLatestTransactions(
existingTransactions: Transaction[],
latestTransactions: Transaction[]
): TransactionUpdates {
latestTransactions: LatestTransaction[]
): TransactionUpdates<LatestTransaction> {
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))
})

Expand All @@ -86,13 +93,31 @@ export function getPruneTransactions(
return { toBeRemoved: transactionsToPrune, toBeStored: [] }
}

export async function storeTransactionUpdates(transactionUpdates: TransactionUpdates) {
export async function storeTransactionUpdates(transactionUpdates: TransactionUpdates<Transaction | LatestTransaction>) {
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)
}
}
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -35,7 +35,7 @@ export const fetchVoyagerTransactions = async (
export async function getLatestTransactions(
accountsToPopulate: WalletAccount[],
metadataTransactions: Transaction[],
) {
): Promise<LatestTransaction[]> {
const transactionsPerAccount = await getTransactionsPerAccount(accountsToPopulate, metadataTransactions)
return Array.from(transactionsPerAccount.values()).flat()
}
48 changes: 30 additions & 18 deletions packages/extension/src/shared/transactions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -94,9 +98,9 @@ export function transactionNamesToTitle(
export async function getTransactionsPerAccount(
accountsToPopulate: WalletAccount[],
metadataTransactions: Transaction[],
): Promise<Map<WalletAccount, Transaction[]>> {
): Promise<Map<WalletAccount, LatestTransaction[]>> {
const getTransactions = buildGetTransactionsFn(metadataTransactions)
const transactionsPerAccount = new Map<WalletAccount, Transaction[]>()
const transactionsPerAccount = new Map<WalletAccount, LatestTransaction[]>()
await Promise.all(
accountsToPopulate.map(async (account) => {
const transactions = await getTransactions(account)
Expand All @@ -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<LatestTransaction[]> => {
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
}
}
}

Expand Down
Loading