diff --git a/src/background/services/history/HistoryService.test.ts b/src/background/services/history/HistoryService.test.ts index 97618dd40..300d68330 100644 --- a/src/background/services/history/HistoryService.test.ts +++ b/src/background/services/history/HistoryService.test.ts @@ -1,21 +1,9 @@ -import { - PChainTransactionType, - XChainTransactionType, -} from '@avalabs/glacier-sdk'; -import { - ChainId, - Network, - NetworkToken, - NetworkVMType, -} from '@avalabs/core-chains-sdk'; +import { Network, NetworkToken, NetworkVMType } from '@avalabs/core-chains-sdk'; import { HistoryService } from './HistoryService'; -import { - PchainTxHistoryItem, - TransactionType, - TxHistoryItem, - XchainTxHistoryItem, -} from './models'; +import { TxHistoryItem } from './models'; import { TokenType } from '@avalabs/vm-module-types'; +import { TransactionType } from '@avalabs/vm-module-types'; +import { ETHEREUM_ADDRESS } from '@src/utils/bridgeTransactionUtils'; describe('src/background/services/history/HistoryService.ts', () => { let service: HistoryService; @@ -37,26 +25,25 @@ describe('src/background/services/history/HistoryService.ts', () => { logoUri: 'test.one.com/logo', primaryColor: 'purple', }; - const networkServiceMock = { - activeNetwork: network1, - } as any; - const btcHistoryServiceMock = { - getHistory: jest.fn(), - } as any; - const ethHistoryServiceMock = { - getHistory: jest.fn(), - } as any; - const historyServicePVMMock = { - getHistory: jest.fn(), + + const moduleManagereMock = { + loadModuleByNetwork: jest.fn(), } as any; - const historyServiceAVMMock = { - getHistory: jest.fn(), + const accountsServiceMock = { + activeAccount: { + addressC: 'addressC', + addressBTC: 'addressBtc', + addressPVM: 'addressBtc', + addressAVM: 'addressBtc', + }, } as any; - const glacierHistoryServiceMock = { - getHistory: jest.fn(), + const bridgeHistoryHelperServiceMock = { + isBridgeTransactionBTC: jest.fn(), } as any; - const glacierServiceMock = { - isNetworkSupported: jest.fn(), + const unifiedBridgeServiceMock = { + state: { + addresses: [], + }, } as any; const txHistoryItem: TxHistoryItem = { @@ -65,7 +52,7 @@ describe('src/background/services/history/HistoryService.ts', () => { isIncoming: false, isOutgoing: true, isSender: true, - timestamp: 'timestamp', + timestamp: 1111, hash: 'hash', from: 'from', to: 'to', @@ -80,7 +67,8 @@ describe('src/background/services/history/HistoryService.ts', () => { gasUsed: 'gasUsed', explorerLink: 'explorerLink', chainId: 'chainId', - type: TransactionType.SEND, + txType: TransactionType.SEND, + vmType: NetworkVMType.EVM, }; const btcTxHistoryItem: TxHistoryItem = { @@ -89,7 +77,7 @@ describe('src/background/services/history/HistoryService.ts', () => { isIncoming: false, isOutgoing: true, isSender: true, - timestamp: 'timestamp', + timestamp: 1111, hash: 'hash', from: 'from', to: 'to', @@ -104,59 +92,18 @@ describe('src/background/services/history/HistoryService.ts', () => { gasUsed: 'gasUsed', explorerLink: 'explorerLink', chainId: 'chainId', - type: TransactionType.SEND, - }; - const pchainTxHistoryItem: PchainTxHistoryItem = { - isSender: true, - timestamp: 'timestamp', - from: ['from'], - to: ['to'], - token: { - name: 'tokenName', - symbol: 'tokenSymbol', - amount: 'amount', - type: TokenType.NATIVE, - }, - gasUsed: 'gasUsed', - explorerLink: 'explorerLink', - chainId: 'chainId', - type: PChainTransactionType.BASE_TX, - vmType: 'PVM', - }; - - const xchainTxHistoryItem: XchainTxHistoryItem = { - ...pchainTxHistoryItem, - type: XChainTransactionType.BASE_TX, - vmType: 'AVM', + txType: TransactionType.SEND, + vmType: NetworkVMType.BITCOIN, }; beforeEach(() => { jest.resetAllMocks(); - networkServiceMock.activeNetwork = network1; - jest - .mocked(btcHistoryServiceMock.getHistory) - .mockResolvedValue([btcTxHistoryItem]); - jest - .mocked(ethHistoryServiceMock.getHistory) - .mockResolvedValue([txHistoryItem]); - jest - .mocked(glacierHistoryServiceMock.getHistory) - .mockResolvedValue([txHistoryItem, btcTxHistoryItem]); - jest.mocked(glacierServiceMock.isNetworkSupported).mockResolvedValue(false); - jest - .mocked(historyServiceAVMMock.getHistory) - .mockResolvedValue([xchainTxHistoryItem]); - jest - .mocked(historyServicePVMMock.getHistory) - .mockResolvedValue([pchainTxHistoryItem]); service = new HistoryService( - btcHistoryServiceMock, - ethHistoryServiceMock, - glacierHistoryServiceMock, - glacierServiceMock, - historyServiceAVMMock, - historyServicePVMMock + moduleManagereMock, + accountsServiceMock, + bridgeHistoryHelperServiceMock, + unifiedBridgeServiceMock ); }); @@ -164,55 +111,96 @@ describe('src/background/services/history/HistoryService.ts', () => { const result = await service.getTxHistory({ vmName: 'hmmmmmm' } as any); expect(result).toEqual([]); }); - it('should return results from glacier if the network is supported', async () => { - jest - .spyOn(glacierServiceMock, 'isNetworkSupported') - .mockResolvedValue(true); - + it('should return empty array when theere is no addres for the network', async () => { const result = await service.getTxHistory({ - chainId: ChainId.ETHEREUM_HOMESTEAD, + vmName: NetworkVMType.CoreEth, } as any); - expect(glacierServiceMock.isNetworkSupported).toHaveBeenCalledTimes(1); - expect(result).toEqual([txHistoryItem, btcTxHistoryItem]); + expect(result).toEqual([]); }); - - it('should return results from btc history service when not supported by glacier and network has bitcoin vmType', async () => { + it('should return empty array when there is no transactions in the past', async () => { + jest.mocked(moduleManagereMock.loadModuleByNetwork).mockResolvedValue({ + getTransactionHistory: jest.fn(() => { + return { transactions: [] }; + }), + }); const result = await service.getTxHistory({ ...network1, vmName: NetworkVMType.BITCOIN, caipId: 'bip122:000000000019d6689c085ae165831e93', }); - expect(btcHistoryServiceMock.getHistory).toHaveBeenCalledTimes(1); - expect(result).toEqual([btcTxHistoryItem]); + expect(result).toEqual([]); }); - it('should return results from btc history service when not supported by glacier and network has bitcoin vmType', async () => { + + it('should return results from btc history', async () => { + jest.mocked(moduleManagereMock.loadModuleByNetwork).mockResolvedValue({ + getTransactionHistory: jest.fn(() => { + return { transactions: [btcTxHistoryItem] }; + }), + }); + jest + .mocked(bridgeHistoryHelperServiceMock.isBridgeTransactionBTC) + .mockReturnValue(false); const result = await service.getTxHistory({ ...network1, vmName: NetworkVMType.BITCOIN, caipId: 'bip122:000000000019d6689c085ae165831e93', }); - expect(btcHistoryServiceMock.getHistory).toHaveBeenCalledTimes(1); expect(result).toEqual([btcTxHistoryItem]); }); - it('should return results from eth history service when not supported by glacier and network has EVM vmType', async () => { + it('should return results with a BTC bridge transaction', async () => { + jest.mocked(moduleManagereMock.loadModuleByNetwork).mockResolvedValue({ + getTransactionHistory: jest.fn(() => { + return { transactions: [btcTxHistoryItem] }; + }), + }); + jest + .mocked(bridgeHistoryHelperServiceMock.isBridgeTransactionBTC) + .mockReturnValue(true); const result = await service.getTxHistory({ - chainId: ChainId.ETHEREUM_HOMESTEAD, - } as any); - expect(ethHistoryServiceMock.getHistory).toHaveBeenCalledTimes(1); - expect(result).toEqual([txHistoryItem]); + ...network1, + vmName: NetworkVMType.BITCOIN, + caipId: 'bip122:000000000019d6689c085ae165831e93', + }); + + expect(result).toEqual([{ ...btcTxHistoryItem, isBridge: true }]); }); - it('should return results from pvm history service when not supported by glacier and isPchainNetwork', async () => { + it('should return results with an ETH bridge transaction', async () => { + jest.mocked(moduleManagereMock.loadModuleByNetwork).mockResolvedValue({ + getTransactionHistory: jest.fn(() => { + return { + transactions: [ + { + ...txHistoryItem, + from: ETHEREUM_ADDRESS, + }, + ], + }; + }), + }); const result = await service.getTxHistory({ - vmName: NetworkVMType.PVM, - } as any); - expect(historyServicePVMMock.getHistory).toHaveBeenCalledTimes(1); - expect(result).toEqual([pchainTxHistoryItem]); + ...network1, + vmName: NetworkVMType.EVM, + caipId: 'caip', + }); + expect(result).toEqual([ + { ...txHistoryItem, isBridge: true, from: ETHEREUM_ADDRESS }, + ]); }); - it('should return results from avm history service when not supported by glacier and network has AVM vmType', async () => { + it('should return results with an pchain transaction', async () => { + jest.mocked(moduleManagereMock.loadModuleByNetwork).mockResolvedValue({ + getTransactionHistory: jest.fn(() => { + return { + transactions: [txHistoryItem], + }; + }), + }); + const result = await service.getTxHistory({ - vmName: NetworkVMType.AVM, - } as any); - expect(historyServiceAVMMock.getHistory).toHaveBeenCalledTimes(1); - expect(result).toEqual([xchainTxHistoryItem]); + ...network1, + vmName: NetworkVMType.PVM, + caipId: 'caip', + }); + + expect(result).toEqual([{ ...txHistoryItem, vmType: 'PVM' }]); }); }); diff --git a/src/background/services/history/HistoryService.ts b/src/background/services/history/HistoryService.ts index a366596f8..15293feeb 100644 --- a/src/background/services/history/HistoryService.ts +++ b/src/background/services/history/HistoryService.ts @@ -1,48 +1,103 @@ import { singleton } from 'tsyringe'; import { NetworkVMType } from '@avalabs/core-chains-sdk'; - import { NetworkWithCaipId } from '../network/models'; -import { GlacierService } from '../glacier/GlacierService'; -import { isPchainNetwork } from '../network/utils/isAvalanchePchainNetwork'; -import { isXchainNetwork } from '../network/utils/isAvalancheXchainNetwork'; -import { isEthereumNetwork } from '../network/utils/isEthereumNetwork'; - -import { HistoryServiceBTC } from './HistoryServiceBTC'; -import { HistoryServiceETH } from './HistoryServiceETH'; -import { HistoryServicePVM } from './HistoryServicePVM'; -import { HistoryServiceAVM } from './HistoryServiceAVM'; -import { HistoryServiceGlacier } from './HistoryServiceGlacier'; +import { ModuleManager } from '@src/background/vmModules/ModuleManager'; +import { AccountsService } from '../accounts/AccountsService'; +import { TxHistoryItem } from './models'; +import { HistoryServiceBridgeHelper } from './HistoryServiceBridgeHelper'; +import { Transaction } from '@avalabs/vm-module-types'; +import { ETHEREUM_ADDRESS } from '@src/utils/bridgeTransactionUtils'; +import { UnifiedBridgeService } from '../unifiedBridge/UnifiedBridgeService'; +import { resolve } from '@src/utils/promiseResolver'; +import sentryCaptureException, { + SentryExceptionTypes, +} from '@src/monitoring/sentryCaptureException'; @singleton() export class HistoryService { constructor( - private btcHistoryService: HistoryServiceBTC, - private ethHistoryService: HistoryServiceETH, - private glacierHistoryService: HistoryServiceGlacier, - private glacierService: GlacierService, - private historyServiceAVM: HistoryServiceAVM, - private historyServicePVM: HistoryServicePVM + private moduleManager: ModuleManager, + private accountsService: AccountsService, + private bridgeHistoryHelperService: HistoryServiceBridgeHelper, + private unifiedBridgeService: UnifiedBridgeService ) {} - async getTxHistory(network: NetworkWithCaipId) { - const isSupportedNetwork = await this.glacierService.isNetworkSupported( - network.chainId + async getTxHistory( + network: NetworkWithCaipId, + otherAddress?: string + ): Promise { + const address = otherAddress ?? this.#getAddress(network); + + if (!address) { + return []; + } + + const [module] = await resolve( + this.moduleManager.loadModuleByNetwork(network) ); - if (isSupportedNetwork) { - return await this.glacierHistoryService.getHistory(network); + if (!module) { + sentryCaptureException( + new Error( + `Fetching history failed. Module not found for ${network.caipId}` + ), + SentryExceptionTypes.VM_MODULES + ); + return []; } + const { transactions } = await module.getTransactionHistory({ + address, + network, + offset: 25, + }); + + const txHistoryItem = transactions.map((transaction) => { + const isBridge = this.#getIsBirdge(network, transaction); + const vmType = network.vmName; + return { ...transaction, vmType, isBridge }; + }) as TxHistoryItem[]; + + return txHistoryItem; + } + #getIsBirdge(network: NetworkWithCaipId, transaction: Transaction) { if (network.vmName === NetworkVMType.BITCOIN) { - return await this.btcHistoryService.getHistory(network); - } else if (isEthereumNetwork(network)) { - return await this.ethHistoryService.getHistory(network); - } else if (isPchainNetwork(network)) { - return await this.historyServicePVM.getHistory(network); - } else if (isXchainNetwork(network)) { - return await this.historyServiceAVM.getHistory(network); + return this.bridgeHistoryHelperService.isBridgeTransactionBTC([ + transaction.from, + transaction.to, + ]); + } + return ( + this.#isBridgeAddress(transaction.from) || + this.#isBridgeAddress(transaction.to) + ); + } + + #getAddress(network: NetworkWithCaipId) { + switch (network.vmName) { + case NetworkVMType.EVM: + return this.accountsService.activeAccount?.addressC; + case NetworkVMType.BITCOIN: + return this.accountsService.activeAccount?.addressBTC; + case NetworkVMType.AVM: + return this.accountsService.activeAccount?.addressAVM; + case NetworkVMType.PVM: + return this.accountsService.activeAccount?.addressPVM; + case NetworkVMType.CoreEth: + return this.accountsService.activeAccount?.addressCoreEth; + default: + return undefined; + } + } + + #isBridgeAddress(address?: string) { + if (!address) { + return false; } - return []; + return [ + ETHEREUM_ADDRESS, + ...this.unifiedBridgeService.state.addresses, + ].includes(address.toLowerCase()); } } diff --git a/src/background/services/history/HistoryServiceAVM.ts b/src/background/services/history/HistoryServiceAVM.ts deleted file mode 100644 index f8130c139..000000000 --- a/src/background/services/history/HistoryServiceAVM.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { singleton } from 'tsyringe'; -import { Network } from '@avalabs/core-chains-sdk'; -import { Avalanche } from '@avalabs/core-wallets-sdk'; -import { AccountsService } from '../accounts/AccountsService'; -import { - BlockchainId, - ListXChainTransactionsResponse, - Network as NetworkType, - PChainTransaction, - PrimaryNetworkChainName, - SortOrder, - XChainLinearTransaction, - XChainNonLinearTransaction, -} from '@avalabs/glacier-sdk'; -import { GlacierService } from '../glacier/GlacierService'; -import { TxHistoryItemToken, XchainTxHistoryItem } from './models'; -import Big from 'big.js'; -import { getAvaxAssetId } from './utils/getAvaxAssetId'; -import { stripAddressPrefix } from '@src/utils/stripAddressPrefix'; -import { getExplorerAddressByNetwork } from '@src/utils/getExplorerAddress'; -import { isXchainNetwork } from '../network/utils/isAvalancheXchainNetwork'; -import { getTokenValue } from '../balances/utils/getTokenValue'; -import { TokenType } from '@avalabs/vm-module-types'; - -@singleton() -export class HistoryServiceAVM { - constructor( - private accountsService: AccountsService, - private glacierService: GlacierService - ) {} - - isXchainTransactions(value): value is ListXChainTransactionsResponse { - return value.chainInfo.chainName === PrimaryNetworkChainName.X_CHAIN; - } - - aggregateValue( - value: PChainTransaction['value'], - isTestnet: boolean - ): Big | undefined { - return value - .filter((value_) => value_.assetId === getAvaxAssetId(isTestnet)) - .reduce( - (accumulator, value_) => accumulator.add(value_.amount), - new Big(0) - ); - } - - convertToTxHistoryItem( - tx: XChainNonLinearTransaction | XChainLinearTransaction, - network: Network, - address: string - ): XchainTxHistoryItem { - const avax = network.networkToken; - - const isImportExport = ['ImportTx', 'ExportTx'].includes(tx.txType); - const xBlockchainId = network.isTestnet - ? Avalanche.FujiContext.xBlockchainID - : Avalanche.MainnetContext.xBlockchainID; - - const importExportAmount = tx.emittedUtxos - .filter( - (utxo) => - utxo.asset.assetId === getAvaxAssetId(!!network.isTestnet) && - ((tx.txType === 'ImportTx' && - utxo.consumedOnChainId === xBlockchainId) || - (tx.txType === 'ExportTx' && - utxo.consumedOnChainId !== xBlockchainId)) - ) - .reduce((agg, utxo) => agg.add(utxo.asset.amount), new Big(0)); - - const totalAmountCreated = tx.amountCreated - .filter((asset) => asset.assetId === getAvaxAssetId(!!network.isTestnet)) - .reduce( - (accumulator, asset) => accumulator.add(asset.amount), - new Big(0) - ); - const totalAmountUnlocked = tx.amountUnlocked - .filter((asset) => asset.assetId === getAvaxAssetId(!!network.isTestnet)) - .reduce( - (accumulator, asset) => accumulator.add(asset.amount), - new Big(0) - ); - - const nAvaxAmt = isImportExport ? importExportAmount : totalAmountCreated; - const avaxAmt = nAvaxAmt - ? new Big(getTokenValue(avax.decimals, nAvaxAmt.toNumber())) - : new Big(0); - - const froms = new Set( - tx.consumedUtxos?.flatMap((utxo) => utxo.addresses) || [] - ); - const tos = new Set( - tx.emittedUtxos?.flatMap((utxo) => utxo.addresses) || [] - ); - const nAvaxFee = totalAmountUnlocked.minus(totalAmountCreated); - const avaxBurnedAmount = nAvaxFee - ? new Big(getTokenValue(avax.decimals, nAvaxFee.toNumber())) - : new Big(0); - - const isSender = froms.has(stripAddressPrefix(address)); - const timestamp = new Date(tx.timestamp * 1000); - - const token: TxHistoryItemToken = { - decimal: avax.decimals.toString(), - name: avax.name, - symbol: avax.symbol, - amount: avaxAmt?.toString() ?? '0', - type: TokenType.NATIVE, - }; - - const result: XchainTxHistoryItem = { - from: Array.from(froms), - to: Array.from(tos), - isSender, - timestamp: timestamp.toISOString(), - token, - gasUsed: avaxBurnedAmount.toString(), - explorerLink: getExplorerAddressByNetwork(network, tx.txHash, 'tx'), - chainId: network.chainId.toString(), - type: tx.txType, - vmType: 'AVM', - }; - - return result; - } - - async getHistory(network: Network, otherAddress?: string) { - if (!network?.chainId || !isXchainNetwork(network)) { - return []; - } - - const address = - otherAddress ?? this.accountsService.activeAccount?.addressAVM; - - if (!address) { - return []; - } - - const param = { - blockchainId: (network.isTestnet - ? Avalanche.FujiContext.xBlockchainID - : Avalanche.MainnetContext.xBlockchainID) as BlockchainId, - network: network.isTestnet ? NetworkType.FUJI : NetworkType.MAINNET, - addresses: address, - pageSize: 25, - sortOrder: SortOrder.DESC, - }; - - try { - const rawResult = - await this.glacierService.listLatestPrimaryNetworkTransactions(param); - - if (!this.isXchainTransactions(rawResult)) { - return []; - } - - const formattedResult = rawResult.transactions.map((tx) => - this.convertToTxHistoryItem(tx, network, address) - ); - - return formattedResult; - } catch { - return []; - } - } -} diff --git a/src/background/services/history/HistoryServiceBTC.ts b/src/background/services/history/HistoryServiceBTC.ts deleted file mode 100644 index efebf387e..000000000 --- a/src/background/services/history/HistoryServiceBTC.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { NetworkVMType } from '@avalabs/core-chains-sdk'; -import { singleton } from 'tsyringe'; -import { AccountsService } from '../accounts/AccountsService'; -import { HistoryServiceBridgeHelper } from './HistoryServiceBridgeHelper'; -import { TransactionType, TxHistoryItem } from './models'; -import { ModuleManager } from '@src/background/vmModules/ModuleManager'; -import { NetworkWithCaipId } from '../network/models'; -import sentryCaptureException, { - SentryExceptionTypes, -} from '@src/monitoring/sentryCaptureException'; - -@singleton() -export class HistoryServiceBTC { - constructor( - private accountsService: AccountsService, - private bridgeHistoryHelperService: HistoryServiceBridgeHelper, - private moduleManager: ModuleManager - ) {} - - async getHistory(network: NetworkWithCaipId): Promise { - if (network?.vmName !== NetworkVMType.BITCOIN) { - return []; - } - const address = this.accountsService.activeAccount?.addressBTC; - - if (!address) { - return []; - } - - try { - const module = await this.moduleManager.loadModuleByNetwork(network); - const { transactions } = await module.getTransactionHistory({ - address, - network, - }); - - return transactions.map((tx) => { - const isBridge = this.bridgeHistoryHelperService.isBridgeTransactionBTC( - [tx.from, tx.to] - ); - - return { - ...tx, - // BitcoinModule is not able to recognize bridge txs at the moment, so we need to do it here. - isBridge, - type: isBridge - ? TransactionType.BRIDGE - : tx.isSender - ? TransactionType.SEND - : TransactionType.RECEIVE, - }; - }); - } catch (error: any) { - sentryCaptureException(error, SentryExceptionTypes.INTERNAL_ERROR); - return []; - } - } -} diff --git a/src/background/services/history/HistoryServiceETH.ts b/src/background/services/history/HistoryServiceETH.ts deleted file mode 100644 index 27b5e5e18..000000000 --- a/src/background/services/history/HistoryServiceETH.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { isEthereumChainId } from '@src/background/services/network/utils/isEthereumNetwork'; -import { Network } from '@avalabs/core-chains-sdk'; - -import { singleton } from 'tsyringe'; -import { AccountsService } from '../accounts/AccountsService'; -import { HistoryServiceBridgeHelper } from './HistoryServiceBridgeHelper'; -import { TransactionType, TxHistoryItem } from './models'; - -import { balanceToDisplayValue } from '@avalabs/core-utils-sdk'; -import { BN } from 'bn.js'; -import { getExplorerAddressByNetwork } from '@src/utils/getExplorerAddress'; -import { - Erc20Tx, - getErc20Txs, - getNormalTxs, - NormalTx, -} from '@avalabs/core-etherscan-sdk'; -import { TokenType } from '@avalabs/vm-module-types'; - -@singleton() -export class HistoryServiceETH { - constructor( - private bridgeHistoryHelperService: HistoryServiceBridgeHelper, - private accountsService: AccountsService - ) {} - - private isContractCall(tx: NormalTx) { - return tx.input !== '0x'; - } - - private convertTransactionERC20( - tx: Erc20Tx, - network: Network - ): TxHistoryItem { - const isSender = - tx.from.toLowerCase() === - this.accountsService.activeAccount?.addressC.toLowerCase(); - const timestamp = new Date(parseInt(tx.timeStamp) * 1000); - const decimals = parseInt(tx.tokenDecimal); - const amountBN = new BN(tx.value); - const amountDisplayValue = balanceToDisplayValue(amountBN, decimals); - const isBridge = this.bridgeHistoryHelperService.isBridgeTransactionEVM( - tx, - network - ); - - return { - isBridge, - isIncoming: !isSender, - isOutgoing: isSender, - isContractCall: false, - timestamp: timestamp.toISOString(), - hash: tx.hash, - isSender: isSender, - from: tx.from, - to: tx.to, - tokens: [ - { - decimal: tx.tokenDecimal, - name: tx.tokenName, - symbol: tx.tokenSymbol, - amount: amountDisplayValue, - type: TokenType.ERC20, - }, - ], - gasPrice: tx.gasPrice, - gasUsed: tx.gasUsed, - explorerLink: getExplorerAddressByNetwork(network, tx.hash), - chainId: network.chainId.toString(), - type: isBridge ? TransactionType.BRIDGE : TransactionType.UNKNOWN, - }; - } - - private convertTransactionNormal( - tx: NormalTx, - network: Network - ): TxHistoryItem { - const isSender = - tx.from.toLowerCase() === - this.accountsService.activeAccount?.addressC.toLowerCase(); - const timestamp = new Date(parseInt(tx.timeStamp) * 1000); - const amountBN = new BN(tx.value); - const amountDisplayValue = balanceToDisplayValue( - amountBN, - network.networkToken.decimals - ); - - return { - isBridge: false, - isIncoming: !isSender, - isOutgoing: isSender, - isContractCall: this.isContractCall(tx), - timestamp: timestamp.toISOString(), - hash: tx.hash, - isSender: isSender, - from: tx.from, - to: tx.to, - tokens: [ - { - decimal: network.networkToken.decimals - ? network.networkToken.decimals.toString() - : '18', - name: network.networkToken.name, - symbol: network.networkToken.symbol, - amount: amountDisplayValue, - type: TokenType.NATIVE, - }, - ], - gasPrice: tx.gasPrice, - gasUsed: tx.gasUsed, - explorerLink: getExplorerAddressByNetwork(network, tx.hash), - chainId: network.chainId.toString(), - type: TransactionType.UNKNOWN, - }; - } - - async getHistory(network: Network) { - if (!network?.chainId || !isEthereumChainId(network.chainId)) { - return []; - } - - const account = this.accountsService.activeAccount?.addressC; - - if (!account) { - return []; - } - - return await this.getHistoryInterleaved(account, network); - } - - private async getHistoryInterleaved( - userAddress: string, - network: Network, - page = 0, - offset = 0 - ) { - const normalHist = ( - await getNormalTxs(userAddress, !network.isTestnet, { page, offset }) - ).map((tx) => this.convertTransactionNormal(tx, network)); - - const erc20Hist = ( - await getErc20Txs(userAddress, !network.isTestnet, undefined, { - page, - offset, - }) - ).map((tx) => this.convertTransactionERC20(tx, network)); - - // Filter erc20 transactions from normal tx list - const erc20TxHashes = erc20Hist.map((tx) => tx.hash); - const filteredNormalTxs = normalHist.filter((tx) => { - return !erc20TxHashes.includes(tx.hash); - }); - - // Sort by timestamp - const joined = [...filteredNormalTxs, ...erc20Hist]; - return joined.sort((a, b) => - (b.timestamp as string).localeCompare(a.timestamp as string) - ); - } -} diff --git a/src/background/services/history/HistoryServiceGlacier.test.ts b/src/background/services/history/HistoryServiceGlacier.test.ts deleted file mode 100644 index 57695aa22..000000000 --- a/src/background/services/history/HistoryServiceGlacier.test.ts +++ /dev/null @@ -1,1060 +0,0 @@ -import { - ChainId, - Network, - NetworkToken, - NetworkVMType, -} from '@avalabs/core-chains-sdk'; -import { - CurrencyCode, - Erc20Token, - Erc20TransferDetails, - NftTokenMetadataStatus, - Erc721Token, - Erc721TransferDetails, - InternalTransactionOpCall, - NativeTransaction, - TransactionDetails, - Glacier, - TransactionMethodType, -} from '@avalabs/glacier-sdk'; -import { ETHEREUM_ADDRESS } from '@src/utils/bridgeTransactionUtils'; -import { getExplorerAddressByNetwork } from '@src/utils/getExplorerAddress'; -import { AccountType } from '../accounts/models'; -import { HistoryServiceGlacier } from './HistoryServiceGlacier'; -import { TransactionType, HistoryItemCategories } from './models'; -import { getSmallImageForNFT } from '../balances/nft/utils/getSmallImageForNFT'; -import { getNftMetadata } from '@src/utils/getNftMetadata'; -import { TokenType } from '@avalabs/vm-module-types'; - -jest.mock('@src/utils/getExplorerAddress', () => ({ - getExplorerAddressByNetwork: jest.fn(), -})); - -jest.mock('@avalabs/glacier-sdk', () => ({ - Glacier: jest.fn(), - CurrencyCode: { - Usd: 'usd', - }, - NftTokenMetadataStatus: { - UNKNOWN: 'UNKNOWN', - }, - InternalTransactionOpCall: { - UNKNOWN: 'UNKNOWN', - }, - TransactionMethodType: { - NATIVE_TRANSFER: 'NATIVE_TRANSFER', - }, - Erc20Token: { - ercType: { - ERC_20: 'ERC-20', - }, - }, - Erc721Token: { - ercType: { - ERC_721: 'ERC-721', - }, - }, -})); - -jest.mock('@src/utils/getNftMetadata', () => ({ - getNftMetadata: jest.fn(), -})); - -jest.mock('../balances/nft/utils/getSmallImageForNFT', () => ({ - getSmallImageForNFT: jest.fn(), -})); -const btcAddress = 'bc1111111111'; -const userAddress = '0xaaaaaaaaaa'; - -const mockedAccountsService = { - activeAccount: { - index: 0, - name: 'test name', - type: AccountType.PRIMARY, - addressBTC: btcAddress, - addressC: userAddress, - }, -} as any; -const mockedUnifiedBridgeService = { - state: { addresses: [] }, -} as any; - -const senderAddress = 'Sender Address'; - -const tokenInfo: Erc20TransferDetails = { - from: { address: senderAddress }, - to: { address: userAddress }, - value: '100000000000000', - erc20Token: { - ercType: Erc20Token.ercType.ERC_20, - address: 'erc20 Token address', - decimals: 18, - name: 'ERC20 Token', - price: { value: 1000000, currencyCode: CurrencyCode.USD }, - symbol: 'E20T', - }, - logIndex: 1, -}; - -const nativeTx: NativeTransaction = { - blockNumber: '339', - blockTimestamp: 1648672486, - blockHash: '0X9999999999', - blockIndex: 0, - txHash: '0x1010101010', - txStatus: '1', - txType: 1, - gasLimit: '51373', - gasUsed: '51373', - gasPrice: '470000000000', - nonce: '1', - from: { - address: '0x1212121212', - }, - to: { - address: '0x1313131313', - }, - method: { - methodHash: '0xa9059cbb', - callType: TransactionMethodType.NATIVE_TRANSFER, - methodName: 'transfer(address,uint256)', - }, - value: '0', -}; - -const detailsForTransfer: TransactionDetails = { - erc20Transfers: [tokenInfo], - nativeTransaction: { - blockNumber: '15583097', - blockTimestamp: 1663779683, - blockHash: 'txBlockHash', - blockIndex: 31, - txHash: '0x1010101010', - txStatus: '1', - txType: 1, - gasLimit: '200000', - gasUsed: '51606', - gasPrice: '20743795546', - nonce: '1', - from: { address: senderAddress }, - to: { - address: 'erc20 Token address', - logoUri: 'erc20 Logo Uri', - name: 'ERC20 Token', - symbol: 'E20T', - }, - method: { - methodHash: '0xa9059cbb', - callType: TransactionMethodType.NATIVE_TRANSFER, - methodName: 'transfer(address,uint256)', - }, - value: '0', - }, -}; - -const networkToken: NetworkToken = { - name: 'network token', - symbol: 'NWT', - description: 'This is network token.', - decimals: 20, - logoUri: 'network.token.logo.uri', -}; - -const network: Network = { - primaryColor: 'blue', - chainName: 'network chain', - chainId: 123, - vmName: NetworkVMType.EVM, - rpcUrl: 'network.rpc.url', - explorerUrl: 'https://explorer.url', - networkToken: networkToken, - logoUri: 'network.logo.uri', -}; - -const historyItemCategories: HistoryItemCategories = { - isBridge: false, - isSwap: false, - isNativeSend: false, - isNativeReceive: false, - isNFTPurchase: false, - isApprove: false, - isTransfer: false, - isAirdrop: false, - isUnwrap: false, - isFillOrder: false, - isContractCall: false, - method: 'test', - type: TransactionType.UNKNOWN, -}; - -const txHistoryItem = { - isBridge: false, - isContractCall: historyItemCategories.isContractCall, - isIncoming: true, - isOutgoing: false, - isSender: false, - timestamp: new Date( - detailsForTransfer.nativeTransaction.blockTimestamp * 1000 - ).toISOString(), - hash: nativeTx.txHash, - from: senderAddress, - to: userAddress, - tokens: [ - { - decimal: tokenInfo.erc20Token.decimals.toString(), - name: tokenInfo.erc20Token.name, - symbol: tokenInfo.erc20Token.symbol, - amount: '0.0001', - from: { address: senderAddress }, - to: { address: userAddress }, - type: TokenType.ERC20, - }, - ], - gasPrice: detailsForTransfer.nativeTransaction.gasPrice, - gasUsed: detailsForTransfer.nativeTransaction.gasUsed, - explorerLink: `test.com/${nativeTx.txHash}`, - chainId: network.chainId.toString(), - type: TransactionType.TRANSFER, -}; - -const imageUri = 'erc721.image.com'; -const smallImageUri = 'erc721.small.image.com'; - -const erc20Token1: Erc20Token = { - address: '0x7777777777', - name: 'Erc20 Token 1', - symbol: 'E20T1', - decimals: 20, - logoUri: 'erc20.one.com', - ercType: Erc20Token.ercType.ERC_20, - price: { - currencyCode: CurrencyCode.USD, - value: 100, - }, -}; - -const erc721Token1: Erc721Token = { - address: '0x8888888888', - name: 'Erc721 Token 1', - symbol: 'E721T1', - tokenId: '123', - tokenUri: 'erc721.one.com', - ercType: Erc721Token.ercType.ERC_721, - metadata: { - indexStatus: NftTokenMetadataStatus.UNINDEXED, - imageUri: imageUri, - }, -}; -const erc721TokenWithNoImageUri: Erc721Token = { - address: '0x123123123123123', - name: 'Erc721 Token no Image', - symbol: 'E721T2', - tokenId: '789', - tokenUri: 'erc721.no.image.com', - ercType: Erc721Token.ercType.ERC_721, - metadata: { - indexStatus: NftTokenMetadataStatus.UNINDEXED, - }, -}; - -const erc20Tx: Erc20TransferDetails = { - from: { - address: '0x3333333333', - }, - to: { - address: '0x4444444444', - }, - erc20Token: erc20Token1, - value: '2', - logIndex: 2, -}; - -const erc721Tx: Erc721TransferDetails = { - from: { - address: '0x5555555555', - }, - to: { - address: '0x6666666666', - }, - erc721Token: erc721Token1, - logIndex: 3, -}; - -const txDetails1: TransactionDetails = { - nativeTransaction: nativeTx, -}; - -const erc20TxWithUserAddress1: Erc20TransferDetails = { - ...erc20Tx, - from: { - address: userAddress, - }, -}; - -const detailsWithInternalTransactions: TransactionDetails = { - nativeTransaction: nativeTx, - internalTransactions: [ - { - from: { address: 'internalFromAddress' }, - to: { address: 'internalToAddress' }, - internalTxType: InternalTransactionOpCall.UNKNOWN, - value: '10000000000000000000', - isReverted: false, - gasLimit: '0', - gasUsed: '21600', - }, - ], -}; - -const detailsForFailedTransaction: TransactionDetails = { - nativeTransaction: { ...nativeTx, txStatus: '0' }, - erc20Transfers: [erc20TxWithUserAddress1], -}; - -describe('background/services/history/HistoryServiceGlacier.test.ts', () => { - beforeEach(() => { - jest.resetAllMocks(); - - (getExplorerAddressByNetwork as jest.Mock).mockImplementation((_, hash) => { - return `test.com/${hash}`; - }); - (getNftMetadata as jest.Mock).mockImplementation(() => - Promise.resolve({ image: imageUri }) - ); - (getSmallImageForNFT as jest.Mock).mockImplementation(() => - Promise.resolve(smallImageUri) - ); - const mockedListTransactions = jest.fn().mockImplementation(() => - Promise.resolve({ - transactions: [ - detailsForTransfer, - detailsWithInternalTransactions, - detailsForFailedTransaction, - txDetails1, - ], - }) - ); - (Glacier as jest.Mock).mockImplementation(() => { - return { - evmTransactions: { - listTransactions: mockedListTransactions, - }, - }; - }); - }); - describe('getHistory', () => { - it('should return an empty array if account is missing', async () => { - const service = new HistoryServiceGlacier( - {} as any, - mockedUnifiedBridgeService - ); - try { - const result = await service.getHistory(network); - expect(result).toStrictEqual([]); - } catch (error) { - fail(error); - } - }); - - it('should return an empty array on error', async () => { - const mockedError = new Error('some error'); - (Glacier as jest.Mock).mockImplementationOnce(() => { - return { - listTransactions: jest.fn().mockRejectedValue(mockedError), - }; - }); - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ); - try { - const result = await service.getHistory(network); - expect(result).toStrictEqual([]); - } catch (e) { - fail(e); - } - }); - - it('should return expected results', async () => { - // detailsWithInternalTransactions, detailsForFailedTransaction, and txDetails1 should be filtered - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ); - try { - const result = await service.getHistory(network); - expect(result.length).toEqual(1); - expect(result[0]).toEqual(txHistoryItem); - } catch (error) { - fail(error); - } - }); - }); - - describe('getAddress', () => { - it('should return btc Address when param is BTC network ID', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getAddress(ChainId.BITCOIN); - expect(result).toEqual(btcAddress); - - const result2 = service.getAddress(ChainId.BITCOIN_TESTNET); - expect(result2).toEqual(btcAddress); - }); - - it('should return C Address when param is non-BTC network ID', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const networks = [ - ChainId.AVALANCHE_MAINNET_ID, - ChainId.AVALANCHE_TESTNET_ID, - ChainId.AVALANCHE_LOCAL_ID, - ChainId.ETHEREUM_HOMESTEAD, - ChainId.ETHEREUM_TEST_RINKEBY, - ChainId.ETHEREUM_TEST_GOERLY, - ChainId.ETHEREUM_TEST_SEPOLIA, - ChainId.SWIMMER, - ChainId.SWIMMER_TESTNET, - ChainId.DFK, - ChainId.DFK_TESTNET, - ChainId.WAGMI_TESTNET, - ChainId.DEXALOT_TESTNET, - ]; - networks.forEach((networkId) => { - const result = service.getAddress(networkId); - expect(result).toEqual(userAddress); - }); - }); - }); - - describe('parseRawMethod', () => { - it('should return method name with startCase', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.parseRawMethod('approve(address,uint256)'); - expect(result).toEqual('Approve'); - }); - it('should return empty string if no method name was provided', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.parseRawMethod(); - expect(result).toEqual(''); - }); - it('should return provided method name if open parentheses is not in method name', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const methodName = 'Hello!'; - const result = service.parseRawMethod(methodName); - expect(result).toEqual(methodName); - }); - }); - - describe('getHistoryItemCategories', () => { - it('should return isBridge as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isBridge).toBeFalsy(); - }); - it('should return isBridge as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - - const bridgeTx = { - ...txDetails1, - erc20Transfers: [{ ...erc20Tx, from: { address: ETHEREUM_ADDRESS } }], - }; - const result = service.getHistoryItemCategories(bridgeTx, userAddress); - expect(result.isBridge).toBeTruthy(); - expect(result.type).toEqual(TransactionType.BRIDGE); - expect(result.isContractCall).toBeFalsy(); - }); - it('should return isSwap as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isSwap).toBeFalsy(); - }); - it('should return isSwap as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const swapNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1414141414', - methodName: 'simple swap(address,uint256)', - }, - }; - const swapTx = { - nativeTransaction: swapNativeTx, - }; - const result = service.getHistoryItemCategories(swapTx, userAddress); - expect(result.isSwap).toBeTruthy(); - expect(result.type).toEqual(TransactionType.SWAP); - expect(result.isContractCall).toBeTruthy(); - }); - it('should return isNativeSend as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isNativeSend).toBeFalsy(); - const sendNativeTx = { - ...nativeTx, - from: { - address: userAddress, - }, - }; - const sendTx = { - nativeTransaction: sendNativeTx, - erc721Transfers: erc721Tx, - }; - const result2 = service.getHistoryItemCategories(sendTx, userAddress); - expect(result2.isNativeSend).toBeFalsy(); - }); - it('should return isNativeSend as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const sendNativeTx = { - ...nativeTx, - from: { - address: userAddress, - }, - }; - const sendTx = { - nativeTransaction: sendNativeTx, - }; - const result = service.getHistoryItemCategories(sendTx, userAddress); - expect(result.isNativeSend).toBeTruthy(); - expect(result.type).toEqual(TransactionType.SEND); - expect(result.isContractCall).toBeFalsy(); - }); - it('should return isNativeReceive as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isNativeReceive).toBeFalsy(); - - const receiveNativeTx = { - ...nativeTx, - to: { - address: userAddress, - }, - }; - const receiveTx = { - nativeTransaction: receiveNativeTx, - erc20Transfers: erc20Tx, - }; - const result2 = service.getHistoryItemCategories(receiveTx, userAddress); - expect(result2.isNativeReceive).toBeFalsy(); - }); - it('should return isNativeReceive as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const receiveNativeTx = { - ...nativeTx, - to: { - address: userAddress, - }, - }; - const receiveTx = { - nativeTransaction: receiveNativeTx, - }; - const result = service.getHistoryItemCategories(receiveTx, userAddress); - expect(result.isNativeReceive).toBeTruthy(); - expect(result.type).toEqual(TransactionType.RECEIVE); - expect(result.isContractCall).toBeFalsy(); - }); - it('should return isNFTPurchase as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isNFTPurchase).toBeFalsy(); - }); - it('should return isNFTPurchase as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const receiveNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1515151515', - methodName: 'Market Buy Orders With Eth', - }, - }; - const receiveTx = { - nativeTransaction: receiveNativeTx, - }; - const result = service.getHistoryItemCategories(receiveTx, userAddress); - expect(result.isNFTPurchase).toBeTruthy(); - expect(result.type).toEqual(TransactionType.NFT_BUY); - expect(result.isContractCall).toBeTruthy(); - - const receiveNativeTx2 = { - ...nativeTx, - method: { - methodHash: '0x1616161616', - methodName: 'Buy NFT', - }, - }; - const receiveTx2 = { - nativeTransaction: receiveNativeTx2, - }; - const result2 = service.getHistoryItemCategories(receiveTx2, userAddress); - expect(result2.isNFTPurchase).toBeTruthy(); - expect(result.type).toEqual(TransactionType.NFT_BUY); - expect(result.isContractCall).toBeTruthy(); - }); - it('should return isApprove as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isApprove).toBeFalsy(); - }); - it('should return isApprove as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const approveNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1717171717', - methodName: 'Approve', - }, - }; - const approveTx = { - nativeTransaction: approveNativeTx, - }; - const result = service.getHistoryItemCategories(approveTx, userAddress); - expect(result.isApprove).toBeTruthy(); - expect(result.type).toEqual(TransactionType.APPROVE); - expect(result.isContractCall).toBeTruthy(); - }); - it('should return isTransfer as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const approveNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1717171717', - methodName: 'Approve', - }, - }; - const approveTx = { - nativeTransaction: approveNativeTx, - }; - const result = service.getHistoryItemCategories(approveTx, userAddress); - expect(result.isTransfer).toBeFalsy(); - }); - it('should return isTransfer as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isTransfer).toBeTruthy(); - expect(result.type).toEqual(TransactionType.TRANSFER); - expect(result.isContractCall).toBeFalsy(); - }); - it('should return isAirdrop as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isAirdrop).toBeFalsy(); - }); - it('should return isAirdrop as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const airdropNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1818181818', - methodName: 'airdrop', - }, - }; - const airdropTx = { - nativeTransaction: airdropNativeTx, - }; - const result = service.getHistoryItemCategories(airdropTx, userAddress); - expect(result.isAirdrop).toBeTruthy(); - expect(result.type).toEqual(TransactionType.UNKNOWN); - expect(result.isContractCall).toBeTruthy(); - }); - it('should return isUnwrap as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isUnwrap).toBeFalsy(); - }); - it('should return isUnwrap as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const unwrapNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1919191919', - methodName: 'unwrap', - }, - }; - const unwrapTx = { - nativeTransaction: unwrapNativeTx, - }; - const result = service.getHistoryItemCategories(unwrapTx, userAddress); - expect(result.isUnwrap).toBeTruthy(); - expect(result.type).toEqual(TransactionType.UNKNOWN); - expect(result.isContractCall).toBeTruthy(); - }); - it('should return isFillOrder as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getHistoryItemCategories(txDetails1, userAddress); - expect(result.isFillOrder).toBeFalsy(); - }); - it('should return isFillOrder as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const fillNativeTx = { - ...nativeTx, - method: { - methodHash: '0x1919191919', - methodName: 'Fill Order', - }, - }; - const fillTx = { - nativeTransaction: fillNativeTx, - }; - const result = service.getHistoryItemCategories(fillTx, userAddress); - expect(result.isFillOrder).toBeTruthy(); - expect(result.type).toEqual(TransactionType.UNKNOWN); - expect(result.isContractCall).toBeTruthy(); - }); - }); - - describe('getSenderInfo', () => { - it('should return isOutgoing as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getSenderInfo( - historyItemCategories, - txDetails1, - userAddress - ); - expect(result.isOutgoing).toBeFalsy(); - }); - it('should return isOutgoing as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const categories = { - ...historyItemCategories, - isNativeSend: true, - }; - - const result = service.getSenderInfo(categories, txDetails1, userAddress); - expect(result.isOutgoing).toBeTruthy(); - - const categories2 = { - ...historyItemCategories, - isTransfer: true, - }; - const sendNativeTx = { - ...nativeTx, - from: { - address: userAddress, - }, - }; - const details = { - nativeTransaction: sendNativeTx, - }; - const result2 = service.getSenderInfo(categories2, details, userAddress); - expect(result2.isOutgoing).toBeTruthy(); - }); - - it('should return isIncoming as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getSenderInfo( - historyItemCategories, - txDetails1, - userAddress - ); - expect(result.isIncoming).toBeFalsy(); - }); - it('should return isIncoming as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const categories = { - ...historyItemCategories, - isNativeReceive: true, - }; - - const result = service.getSenderInfo(categories, txDetails1, userAddress); - expect(result.isIncoming).toBeTruthy(); - - const categories2 = { - ...historyItemCategories, - isTransfer: true, - }; - const receiveNativeTx = { - ...nativeTx, - to: { - address: userAddress, - }, - }; - const details = { - nativeTransaction: receiveNativeTx, - }; - const result2 = service.getSenderInfo(categories2, details, userAddress); - expect(result2.isIncoming).toBeTruthy(); - }); - it('should return isSender as false', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getSenderInfo( - historyItemCategories, - txDetails1, - userAddress - ); - expect(result.isSender).toBeFalsy(); - }); - it('should return isSender as true', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const sendNativeTx = { - ...nativeTx, - from: { - address: userAddress, - }, - }; - const details = { - nativeTransaction: sendNativeTx, - }; - - const result = service.getSenderInfo( - historyItemCategories, - details, - userAddress - ); - expect(result.isSender).toBeTruthy(); - }); - it('should return expected from and to', () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = service.getSenderInfo( - historyItemCategories, - txDetails1, - userAddress - ); - expect(result.from).toEqual(txDetails1.nativeTransaction.from.address); - expect(result.to).toEqual(txDetails1.nativeTransaction.to.address); - }); - }); - - describe('getTokens', () => { - it('should return empty array when no tokens are available', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = await service.getTokens(txDetails1, network, userAddress); - expect(result.length).toEqual(0); - }); - it('should return expected nativeToken info when nativeTransaction has a value', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const nativeTxWithValue = { - ...nativeTx, - value: '100000000000000000000', - }; - const details: TransactionDetails = { - nativeTransaction: nativeTxWithValue, - }; - - const result = await service.getTokens(details, network, userAddress); - - expect(result.length).toEqual(1); - expect(result[0]).toEqual({ - decimal: network.networkToken.decimals.toString(), - name: network.networkToken.name, - symbol: network.networkToken.symbol, - amount: '1', - from: nativeTx.from, - to: nativeTx.to, - type: TokenType.NATIVE, - }); - }); - - it('should return token info from erc20Transfer', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = await service.getTokens( - { ...txDetails1, erc20Transfers: [erc20Tx] }, - network, - userAddress - ); - - expect(result.length).toEqual(1); - expect(result[0]).toEqual({ - decimal: erc20Tx.erc20Token.decimals.toString(), - name: erc20Tx.erc20Token.name, - symbol: erc20Tx.erc20Token.symbol, - amount: '0.00000000000000000002', - from: erc20Tx.from, - to: erc20Tx.to, - imageUri: 'erc20.one.com', - type: TokenType.ERC20, - }); - }); - - it('should return token info from erc721Transfer', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = await service.getTokens( - { ...txDetails1, erc721Transfers: [erc721Tx] }, - network, - userAddress - ); - - expect(result.length).toEqual(1); - expect(result[0]).toEqual({ - name: erc721Tx.erc721Token.name, - symbol: erc721Tx.erc721Token.symbol, - amount: '1', - from: erc721Tx.from, - to: erc721Tx.to, - collectableTokenId: erc721Token1.tokenId, - imageUri, - type: TokenType.ERC721, - }); - }); - - it('should return token info with imageUri from fetched metadata', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - - const result = await service.getTokens( - { - ...txDetails1, - erc721Transfers: [ - { ...erc721Tx, erc721Token: erc721TokenWithNoImageUri }, - ], - }, - network, - userAddress - ); - expect(getNftMetadata).toBeCalledTimes(1); - expect(getSmallImageForNFT).toBeCalledTimes(1); - expect(getSmallImageForNFT).toBeCalledWith(imageUri); - - expect(result.length).toEqual(1); - expect(result[0].imageUri).toEqual(smallImageUri); - }); - - it('should return token info with empty imageUri if fails to fetch metadata', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - - (getNftMetadata as jest.Mock).mockImplementation(() => - Promise.reject(new Error('Failed to fetch metadata')) - ); - - const result = await service.getTokens( - { - ...txDetails1, - erc721Transfers: [ - { ...erc721Tx, erc721Token: erc721TokenWithNoImageUri }, - ], - }, - network, - userAddress - ); - expect(getNftMetadata).toBeCalledTimes(1); - - expect(result.length).toEqual(1); - expect(result[0].imageUri).toEqual(''); - }); - }); - - describe('convertToTxHistoryItem', () => { - it('should return expected value', async () => { - const service = new HistoryServiceGlacier( - mockedAccountsService, - mockedUnifiedBridgeService - ) as any; - const result = await service.convertToTxHistoryItem( - detailsForTransfer, - network, - userAddress - ); - expect(result).toEqual(txHistoryItem); - }); - }); -}); diff --git a/src/background/services/history/HistoryServiceGlacier.ts b/src/background/services/history/HistoryServiceGlacier.ts deleted file mode 100644 index b72155829..000000000 --- a/src/background/services/history/HistoryServiceGlacier.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { BN } from 'bn.js'; -import { Network } from '@avalabs/core-chains-sdk'; -import { Glacier, TransactionDetails } from '@avalabs/glacier-sdk'; -import { ETHEREUM_ADDRESS } from '@src/utils/bridgeTransactionUtils'; -import { startCase } from 'lodash'; -import { singleton } from 'tsyringe'; -import { AccountsService } from '../accounts/AccountsService'; -import { isBitcoinChainId } from '../network/utils/isBitcoinNetwork'; -import { - HistoryItemCategories, - NonContractCallTypes, - TransactionType, - TxHistoryItem, - TxHistoryItemToken, -} from './models'; -import { getExplorerAddressByNetwork } from '@src/utils/getExplorerAddress'; -import { balanceToDisplayValue } from '@avalabs/core-utils-sdk'; -import { getNftMetadata } from '@src/utils/getNftMetadata'; -import { getSmallImageForNFT } from '../balances/nft/utils/getSmallImageForNFT'; -import { resolve } from '@src/utils/promiseResolver'; -import { UnifiedBridgeService } from '../unifiedBridge/UnifiedBridgeService'; -import { TokenType } from '@avalabs/vm-module-types'; - -@singleton() -export class HistoryServiceGlacier { - private glacierSdkInstance = new Glacier({ - BASE: process.env.GLACIER_URL, - }); - constructor( - private accountsService: AccountsService, - private unifiedBridgeService: UnifiedBridgeService - ) {} - - async getHistory(network: Network): Promise { - const account = this.getAddress(network.chainId); - - if (!account) { - return []; - } - - try { - const response = - await this.glacierSdkInstance.evmTransactions.listTransactions({ - chainId: network.chainId.toString(), - address: account, - pageSize: 25, - }); - - const convertedItems = await Promise.all( - response.transactions - .filter( - // Currently not showing failed tx - (tranasaction) => tranasaction.nativeTransaction.txStatus === '1' - ) - .map((transaction) => - this.convertToTxHistoryItem(transaction, network, account).then( - (result) => result - ) - ) - ); - - const result = convertedItems.filter( - // Filtering txs with 0 value since there is no change in balance - (transaction) => - transaction.tokens.find((token) => Number(token.amount) !== 0) - ); - - return result; - } catch (err) { - return []; - } - } - - private getAddress(chainId: number) { - return isBitcoinChainId(chainId) - ? this.accountsService.activeAccount?.addressBTC - : this.accountsService.activeAccount?.addressC; - } - - private parseRawMethod(method = '') { - if (method.includes('(')) { - return startCase(method.split('(', 1)[0]); - } - - return method; - } - - private isBridgeAddress(address?: string) { - if (!address) { - return false; - } - - return [ - ETHEREUM_ADDRESS, - ...this.unifiedBridgeService.state.addresses, - ].includes(address.toLowerCase()); - } - - private getHistoryItemCategories( - { nativeTransaction, erc20Transfers, erc721Transfers }: TransactionDetails, - address: string - ): HistoryItemCategories { - const nativeOnly = !erc20Transfers && !erc721Transfers; - const method = this.parseRawMethod(nativeTransaction.method?.methodName); - - const isBridge = - this.isBridgeAddress(erc20Transfers?.[0]?.from?.address) || - this.isBridgeAddress(erc20Transfers?.[0]?.to?.address); - - const isSwap = method.toLowerCase().includes('swap'); - const isNativeSend = - nativeOnly && nativeTransaction.from.address === address; - const isNativeReceive = - nativeOnly && nativeTransaction.to.address === address; - const isNFTPurchase = - method === 'Market Buy Orders With Eth' || method === 'Buy NFT'; - const isApprove = method === 'Approve'; - const isTransfer = method.toLowerCase().includes('transfer'); - const isAirdrop = method.toLowerCase().includes('airdrop'); - const isUnwrap = method.toLowerCase().includes('unwrap'); - const isFillOrder = method === 'Fill Order'; - const type = isBridge - ? TransactionType.BRIDGE - : isSwap - ? TransactionType.SWAP - : isNativeSend - ? TransactionType.SEND - : isNativeReceive - ? TransactionType.RECEIVE - : isNFTPurchase - ? TransactionType.NFT_BUY - : isApprove - ? TransactionType.APPROVE - : isTransfer - ? TransactionType.TRANSFER - : TransactionType.UNKNOWN; - const isContractCall = !NonContractCallTypes.includes(type); - - return { - isBridge, - isSwap, - isNativeSend, - isNativeReceive, - isNFTPurchase, - isApprove, - isTransfer, - isAirdrop, - isUnwrap, - isFillOrder, - isContractCall, - method, - type, - }; - } - - private getSenderInfo( - { isNativeSend, isNativeReceive, isTransfer }: HistoryItemCategories, - { nativeTransaction, erc20Transfers, erc721Transfers }: TransactionDetails, - address: string - ) { - let from = nativeTransaction?.from?.address; - let to = nativeTransaction?.to?.address; - - // Until multi tokens transaction is supported in UI, using from and to of the only token is helpful for UI - if (isTransfer && erc20Transfers && erc20Transfers[0]) { - from = erc20Transfers[0].from.address; - to = erc20Transfers[0].to.address; - } - - if (isTransfer && erc721Transfers && erc721Transfers[0]) { - from = erc721Transfers[0].from.address; - to = erc721Transfers[0].to.address; - } - - const isOutgoing = isNativeSend || (isTransfer && from === address); - const isIncoming = isNativeReceive || (isTransfer && to === address); - - const isSender = from === address; - - return { - isOutgoing, - isIncoming, - isSender, - from, - to, - }; - } - - private async getTokens( - { nativeTransaction, erc20Transfers, erc721Transfers }: TransactionDetails, - network: Network - ): Promise { - const result: TxHistoryItemToken[] = []; - - if (nativeTransaction.value !== '0') { - const decimal = network.networkToken.decimals ?? 18; - const amountBN = new BN(nativeTransaction.value); - const amountDisplayValue = balanceToDisplayValue(amountBN, decimal); - result.push({ - decimal: decimal.toString(), - name: network.networkToken.name, - symbol: network.networkToken.symbol, - amount: amountDisplayValue, - from: nativeTransaction.from, - to: nativeTransaction.to, - type: TokenType.NATIVE, - }); - } - - erc20Transfers?.forEach((erc20Transfer) => { - const decimals = erc20Transfer.erc20Token.decimals; - const amountBN = new BN(erc20Transfer.value); - const amountDisplayValue = balanceToDisplayValue(amountBN, decimals); - - result.push({ - decimal: decimals.toString(), - name: erc20Transfer.erc20Token.name, - symbol: erc20Transfer.erc20Token.symbol, - amount: amountDisplayValue, - from: erc20Transfer.from, - to: erc20Transfer.to, - imageUri: erc20Transfer.erc20Token.logoUri, - type: TokenType.ERC20, - }); - }); - - if (erc721Transfers) { - await Promise.all( - erc721Transfers.map(async (erc721Transfer) => { - const token = erc721Transfer.erc721Token; - let imageUri: string; - - if (token.metadata.imageUri) { - imageUri = token.metadata.imageUri; - } else { - const [metadata, error] = await resolve( - getNftMetadata(token.tokenUri) - ); - if (error) { - imageUri = ''; - } else { - imageUri = metadata.image - ? await getSmallImageForNFT(metadata.image) - : ''; - } - } - - result.push({ - name: erc721Transfer.erc721Token.name, - symbol: erc721Transfer.erc721Token.symbol, - amount: '1', - imageUri, - from: erc721Transfer.from, - to: erc721Transfer.to, - collectableTokenId: erc721Transfer.erc721Token.tokenId, - type: TokenType.ERC721, - }); - }) - ); - } - - return result; - } - - private async convertToTxHistoryItem( - tx: TransactionDetails, - network: Network, - account: string - ): Promise { - const historyItemCategories = this.getHistoryItemCategories(tx, account); - - const { isOutgoing, isIncoming, isSender, from, to } = this.getSenderInfo( - historyItemCategories, - tx, - account - ); - - const tokens = await this.getTokens(tx, network); - - return { - isBridge: historyItemCategories.isBridge, - isContractCall: historyItemCategories.isContractCall, - isIncoming, - isOutgoing, - isSender, - timestamp: new Date( - tx.nativeTransaction.blockTimestamp * 1000 - ).toISOString(), // s to ms - hash: tx.nativeTransaction.txHash, - from, - to, - tokens, - gasPrice: tx.nativeTransaction.gasPrice, - gasUsed: tx.nativeTransaction.gasUsed, - explorerLink: getExplorerAddressByNetwork( - network, - tx.nativeTransaction.txHash - ), - chainId: network.chainId.toString(), - type: historyItemCategories.type, - }; - } -} diff --git a/src/background/services/history/HistoryServicePVM.ts b/src/background/services/history/HistoryServicePVM.ts deleted file mode 100644 index 525bb6292..000000000 --- a/src/background/services/history/HistoryServicePVM.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { singleton } from 'tsyringe'; -import { Network } from '@avalabs/core-chains-sdk'; -import { Avalanche } from '@avalabs/core-wallets-sdk'; -import { isPchainNetwork } from '../network/utils/isAvalanchePchainNetwork'; -import { AccountsService } from '../accounts/AccountsService'; -import { - BlockchainId, - ListPChainTransactionsResponse, - Network as NetworkType, - PChainTransaction, - PrimaryNetworkChainName, - SortOrder, -} from '@avalabs/glacier-sdk'; -import { GlacierService } from '../glacier/GlacierService'; -import { PchainTxHistoryItem, TxHistoryItemToken } from './models'; -import Big from 'big.js'; -import { getAvaxAssetId } from './utils/getAvaxAssetId'; -import { isEmpty } from 'lodash'; -import { stripAddressPrefix } from '@src/utils/stripAddressPrefix'; -import { getExplorerAddressByNetwork } from '@src/utils/getExplorerAddress'; -import { getTokenValue } from '../balances/utils/getTokenValue'; -import { TokenType } from '@avalabs/vm-module-types'; - -@singleton() -export class HistoryServicePVM { - constructor( - private accountsService: AccountsService, - private glacierService: GlacierService - ) {} - - isPchainTransactions(value): value is ListPChainTransactionsResponse { - return value.chainInfo.chainName === PrimaryNetworkChainName.P_CHAIN; - } - - aggregateValue( - value: PChainTransaction['value'], - isTestnet: boolean - ): Big | undefined { - return value - .filter((value_) => value_.assetId === getAvaxAssetId(isTestnet)) - .reduce( - (accumulator, value_) => accumulator.add(value_.amount), - new Big(0) - ); - } - - convertToTxHistoryItem( - tx: PChainTransaction, - network: Network, - address: string - ): PchainTxHistoryItem { - const avax = network.networkToken; - - const froms = new Set( - tx.consumedUtxos.flatMap((utxo) => utxo.addresses) || [] - ); - const tos = new Set( - tx.emittedUtxos.flatMap((utxo) => utxo.addresses) || [] - ); - - const isImportExport = ['ImportTx', 'ExportTx'].includes(tx.txType); - const isBaseTx = tx.txType === 'BaseTx'; - - const nonChangeEmittedUtxosAmt = tx.emittedUtxos - .filter( - (utxo) => - utxo.asset.assetId === getAvaxAssetId(!!network.isTestnet) && - !utxo.addresses.some((addr) => froms.has(addr)) - ) - .reduce((agg, utxo) => agg.add(utxo.asset.amount), new Big(0)); - const txValue = tx.value.find( - (val) => val.assetId === getAvaxAssetId(!!network.isTestnet) - )?.amount; - // This ternary attempts to cover the case where users send themselves AVAX - // in which case the senders are the recipients and we should use the total tx value. - const baseTxValue = nonChangeEmittedUtxosAmt.gt(new Big(0)) - ? nonChangeEmittedUtxosAmt - : txValue - ? new Big(txValue) - : new Big(0) ?? new Big(0); - - const pBlockchainId = network.isTestnet - ? Avalanche.FujiContext.pBlockchainID - : Avalanche.MainnetContext.pBlockchainID; - - const importExportAmount = tx.emittedUtxos - .filter( - (utxo) => - utxo.asset.assetId === getAvaxAssetId(!!network.isTestnet) && - ((tx.txType === 'ImportTx' && - utxo.consumedOnChainId === pBlockchainId) || - (tx.txType === 'ExportTx' && - utxo.consumedOnChainId !== pBlockchainId)) - ) - .reduce((agg, utxo) => agg.add(utxo.amount), new Big(0)); - - const nAvaxAmt = isBaseTx - ? baseTxValue - : isImportExport - ? importExportAmount - : isEmpty(tx.amountStaked) - ? this.aggregateValue(tx.value, !!network.isTestnet) - : this.aggregateValue(tx.amountStaked, !!network.isTestnet); - - const avaxAmt = nAvaxAmt - ? getTokenValue(avax.decimals, nAvaxAmt.toNumber()) - : new Big(0); - - const nAvaxFee = tx.amountBurned - ?.filter((value) => value.assetId === getAvaxAssetId(!!network.isTestnet)) - .reduce( - (accumulator, value) => accumulator.add(value.amount), - new Big(0) - ); - const avaxBurnedAmount = nAvaxFee - ? getTokenValue(avax.decimals, nAvaxFee.toNumber()) - : new Big(0); - - const isSender = froms.has(stripAddressPrefix(address)); - const timestamp = new Date(tx.blockTimestamp * 1000); - - const token: TxHistoryItemToken = { - decimal: avax.decimals.toString(), - name: avax.name, - symbol: avax.symbol, - amount: avaxAmt?.toString() ?? '0', - type: TokenType.NATIVE, - }; - - const result: PchainTxHistoryItem = { - from: Array.from(froms), - to: Array.from(tos), - isSender, - timestamp: timestamp.toISOString(), - token, - gasUsed: avaxBurnedAmount.toString(), - explorerLink: getExplorerAddressByNetwork(network, tx.txHash, 'tx'), - chainId: network.chainId.toString(), - type: tx.txType, - vmType: 'PVM', - }; - - return result; - } - - async getHistory(network: Network, otherAddress?: string) { - if (!network?.chainId || !isPchainNetwork(network)) { - return []; - } - - const address = - otherAddress ?? this.accountsService.activeAccount?.addressPVM; - - if (!address) { - return []; - } - - const param = { - blockchainId: (network.isTestnet - ? Avalanche.FujiContext.pBlockchainID - : Avalanche.MainnetContext.pBlockchainID) as BlockchainId, - network: network.isTestnet ? NetworkType.FUJI : NetworkType.MAINNET, - addresses: address, - pageSize: 25, - sortOrder: SortOrder.DESC, - }; - - try { - const rawResult = - await this.glacierService.listLatestPrimaryNetworkTransactions(param); - - if (!this.isPchainTransactions(rawResult)) { - return []; - } - - const formattedResult = rawResult.transactions.map((tx) => - this.convertToTxHistoryItem(tx, network, address) - ); - - return formattedResult; - } catch { - return []; - } - } -} diff --git a/src/background/services/history/handlers/getHistory.ts b/src/background/services/history/handlers/getHistory.ts index 1bd033aaa..6a9d47b47 100644 --- a/src/background/services/history/handlers/getHistory.ts +++ b/src/background/services/history/handlers/getHistory.ts @@ -1,4 +1,3 @@ -import { PchainTxHistoryItem, XchainTxHistoryItem } from './../models'; import { ExtensionRequest } from '@src/background/connections/extensionConnection/models'; import { ExtensionRequestHandler } from '@src/background/connections/models'; import { injectable } from 'tsyringe'; @@ -8,7 +7,7 @@ import { NetworkService } from '../../network/NetworkService'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.HISTORY_GET, - TxHistoryItem[] | PchainTxHistoryItem[] | XchainTxHistoryItem[] + TxHistoryItem[] >; @injectable() diff --git a/src/background/services/history/models.ts b/src/background/services/history/models.ts index d57a3d496..e2bafafa9 100644 --- a/src/background/services/history/models.ts +++ b/src/background/services/history/models.ts @@ -1,70 +1,12 @@ import { - PChainTransactionType, - RichAddress, - XChainTransactionType, -} from '@avalabs/glacier-sdk'; -import { TokenType } from '@avalabs/vm-module-types'; + NetworkVMType, + Transaction, + TransactionType, +} from '@avalabs/vm-module-types'; -export interface TxHistoryItemToken { - decimal?: string; - name: string; - symbol: string; - amount: string; - imageUri?: string; - from?: RichAddress; - to?: RichAddress; - collectableTokenId?: string; - type: TokenType; -} -export interface TxHistoryItem { +export interface TxHistoryItem extends Transaction { isBridge: boolean; - isContractCall: boolean; - isIncoming: boolean; - isOutgoing: boolean; - isSender: boolean; - timestamp: string | number; - hash: string; - from: string; - to: string; - tokens: TxHistoryItemToken[]; - gasPrice?: string; - gasUsed: string; - explorerLink: string; - chainId: string; // chainId from ActiveNetwork used to fetch tx - type: TransactionType; -} - -export interface XPchainTxHistoryItem { - from: string[]; - to: string[]; - isSender: boolean; - timestamp: string; - token: TxHistoryItemToken; - gasUsed: string; - explorerLink: string; - chainId: string; // chainId from ActiveNetwork used to fetch tx - type: PChainTransactionType | XChainTransactionType; -} - -export interface PchainTxHistoryItem extends XPchainTxHistoryItem { - type: PChainTransactionType; - vmType: 'PVM'; -} -export interface XchainTxHistoryItem extends XPchainTxHistoryItem { - type: XChainTransactionType; - vmType: 'AVM'; -} - -export enum TransactionType { - BRIDGE = 'Bridge', - SWAP = 'Swap', - SEND = 'Send', - RECEIVE = 'Receive', - NFT_BUY = 'NFT Buy', - APPROVE = 'Approve', - TRANSFER = 'Transfer', - BASE_TX = 'BaseTx', - UNKNOWN = 'Unknown', + vmType?: NetworkVMType; } export const NonContractCallTypes = [ diff --git a/src/background/services/history/utils/isTxHistoryItem.test.ts b/src/background/services/history/utils/isTxHistoryItem.test.ts index 3b8bcf8a1..f59dfd22e 100644 --- a/src/background/services/history/utils/isTxHistoryItem.test.ts +++ b/src/background/services/history/utils/isTxHistoryItem.test.ts @@ -1,15 +1,11 @@ -import { TokenType } from '@avalabs/vm-module-types'; -import { - PchainTxHistoryItem, - TransactionType, - TxHistoryItem, - XchainTxHistoryItem, -} from '../models'; +import { TxHistoryItem } from '../models'; +import { NetworkVMType, TokenType } from '@avalabs/vm-module-types'; import { isPchainTxHistoryItem, isTxHistoryItem } from './isTxHistoryItem'; import { PChainTransactionType, XChainTransactionType, } from '@avalabs/glacier-sdk'; +import { TransactionType } from '@avalabs/vm-module-types'; describe('src/background/services/history/utils/isTxHistoryItem.ts', () => { const txHistoryItem: TxHistoryItem = { @@ -18,7 +14,7 @@ describe('src/background/services/history/utils/isTxHistoryItem.ts', () => { isIncoming: false, isOutgoing: true, isSender: true, - timestamp: 'timestamp', + timestamp: 1111, hash: 'hash', from: 'from', to: 'to', @@ -33,30 +29,37 @@ describe('src/background/services/history/utils/isTxHistoryItem.ts', () => { gasUsed: 'gasUsed', explorerLink: 'explorerLink', chainId: 'chainId', - type: TransactionType.SEND, + txType: TransactionType.SEND, }; - const pchainTxHistoryItem: PchainTxHistoryItem = { + const pchainTxHistoryItem: TxHistoryItem = { + isBridge: false, + isContractCall: true, + isIncoming: false, + isOutgoing: true, isSender: true, - timestamp: 'timestamp', - from: ['from'], - to: ['to'], - token: { - name: 'tokenName', - symbol: 'tokenSymbol', - amount: 'amount', - type: TokenType.NATIVE, - }, + timestamp: 111111, + from: 'from', + to: 'to', + hash: 'hash', + tokens: [ + { + name: 'tokenName', + symbol: 'tokenSymbol', + amount: 'amount', + type: TokenType.NATIVE, + }, + ], gasUsed: 'gasUsed', explorerLink: 'explorerLink', chainId: 'chainId', - type: PChainTransactionType.BASE_TX, - vmType: 'PVM', + txType: PChainTransactionType.BASE_TX, + vmType: NetworkVMType.PVM, }; - const xchainTxHistoryItem: XchainTxHistoryItem = { + const xchainTxHistoryItem: TxHistoryItem = { ...pchainTxHistoryItem, - type: XChainTransactionType.BASE_TX, - vmType: 'AVM', + txType: XChainTransactionType.BASE_TX, + vmType: NetworkVMType.AVM, }; beforeEach(() => { diff --git a/src/background/services/history/utils/isTxHistoryItem.ts b/src/background/services/history/utils/isTxHistoryItem.ts index 513bade3b..dfcd3b73b 100644 --- a/src/background/services/history/utils/isTxHistoryItem.ts +++ b/src/background/services/history/utils/isTxHistoryItem.ts @@ -1,19 +1,14 @@ -import { - PchainTxHistoryItem, - TxHistoryItem, - XchainTxHistoryItem, -} from '../models'; +import { TxHistoryItem } from '../models'; -export function isTxHistoryItem( - tx: TxHistoryItem | PchainTxHistoryItem | XchainTxHistoryItem -): tx is TxHistoryItem { - return Object.keys(tx).includes('tokens'); +export function isTxHistoryItem(tx: TxHistoryItem): tx is TxHistoryItem { + if ('isBridge' in tx && tx.vmType !== 'AVM' && tx.vmType !== 'PVM') { + return true; + } + return false; } -export function isPchainTxHistoryItem( - tx: TxHistoryItem | PchainTxHistoryItem | XchainTxHistoryItem -): tx is PchainTxHistoryItem { - if (isTxHistoryItem(tx)) { +export function isPchainTxHistoryItem(tx: TxHistoryItem): tx is TxHistoryItem { + if (!('vmType' in tx)) { return false; } return tx.vmType === 'PVM'; diff --git a/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.test.ts b/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.test.ts index 6bc02efe1..f16bce557 100644 --- a/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.test.ts +++ b/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.test.ts @@ -19,8 +19,7 @@ describe('src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded const getBalancesForNetworks = jest.fn(); const getNetwork = jest.fn(); const addFavoriteNetwork = jest.fn(); - const getHistoryP = jest.fn(); - const getHistoryX = jest.fn(); + const getTxHistory = jest.fn(); beforeEach(() => { jest.resetAllMocks(); @@ -29,8 +28,8 @@ describe('src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded .spyOn(container, 'resolve') .mockReturnValueOnce({ getBalancesForNetworks }) .mockReturnValueOnce({ getNetwork, addFavoriteNetwork }) - .mockReturnValueOnce({ getHistory: getHistoryP }) - .mockReturnValueOnce({ getHistory: getHistoryX }); + .mockReturnValueOnce({ getTxHistory }) + .mockReturnValueOnce({ getTxHistory }); }); it('adds X-Chain if there is balance on one of the accounts', async () => { @@ -80,7 +79,7 @@ describe('src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded nfts: {}, }); - getHistoryP.mockResolvedValueOnce([{ anything: ':-)' } as any]); // P-chain info is fetched first + getTxHistory.mockResolvedValueOnce([{ anything: ':-)' } as any]); // P-chain info is fetched first await addXPChainToFavoriteIfNeeded(accounts as any); @@ -96,11 +95,11 @@ describe('src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded nft: {}, }); - getHistoryP + getTxHistory .mockRejectedValueOnce([]) // P-chain call for account 1 .mockRejectedValueOnce([]); // P-chain call for account 2 - getHistoryX.mockResolvedValueOnce([{ anything: ':-)' } as any]); // X-chain call for account 1 + getTxHistory.mockResolvedValueOnce([{ anything: ':-)' } as any]); // X-chain call for account 1 await addXPChainToFavoriteIfNeeded(accounts as any); diff --git a/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts b/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts index 77dfdfcce..c814bf2ab 100644 --- a/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts +++ b/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts @@ -3,11 +3,10 @@ import { NetworkService } from '../../network/NetworkService'; import { ChainId } from '@avalabs/core-chains-sdk'; import { Balances } from '../../balances/models'; import { PrimaryAccount } from '../../accounts/models'; -import { Network } from '../../network/models'; +import { NetworkWithCaipId } from '../../network/models'; import { isString } from 'lodash'; import { container } from 'tsyringe'; -import { HistoryServicePVM } from '../../history/HistoryServicePVM'; -import { HistoryServiceAVM } from '../../history/HistoryServiceAVM'; +import { HistoryService } from '../../history/HistoryService'; import { TokenWithBalanceAVM, TokenWithBalancePVM, @@ -18,8 +17,7 @@ export const addXPChainToFavoriteIfNeeded = async ( ) => { const balanceService = container.resolve(BalanceAggregatorService); const networkService = container.resolve(NetworkService); - const historyServiceP = container.resolve(HistoryServicePVM); - const historyServiceX = container.resolve(HistoryServiceAVM); + const historyService = container.resolve(HistoryService); const balances = await balanceService.getBalancesForNetworks( [ChainId.AVALANCHE_P, ChainId.AVALANCHE_X], accounts @@ -32,7 +30,7 @@ export const addXPChainToFavoriteIfNeeded = async ( if (pChain) { const hasPActivity = await hasChainActivity( - historyServiceP, + historyService, accounts.map(({ addressPVM }) => addressPVM).filter(isString), pChain ); @@ -50,7 +48,7 @@ export const addXPChainToFavoriteIfNeeded = async ( if (xChain) { const hasXActivity = await hasChainActivity( - historyServiceX, + historyService, accounts.map(({ addressAVM }) => addressAVM).filter(isString), xChain ); @@ -63,13 +61,13 @@ export const addXPChainToFavoriteIfNeeded = async ( }; async function hasChainActivity( - historyService: HistoryServiceAVM | HistoryServicePVM, + historyService: HistoryService, addresses: string[], - network: Network + network: NetworkWithCaipId ) { try { const results = await Promise.allSettled( - addresses.map((address) => historyService.getHistory(network, address)) + addresses.map((address) => historyService.getTxHistory(network, address)) ); const histories = results.map((result) => result.status === 'fulfilled' ? result.value : [] diff --git a/src/background/vmModules/ModuleManager.test.ts b/src/background/vmModules/ModuleManager.test.ts index 2ccdfacea..740b34d83 100644 --- a/src/background/vmModules/ModuleManager.test.ts +++ b/src/background/vmModules/ModuleManager.test.ts @@ -1,5 +1,4 @@ import { NetworkVMType } from '@avalabs/core-chains-sdk'; - import { ModuleManager } from './ModuleManager'; import { VMModuleError } from './models'; import { ApprovalController } from './ApprovalController'; diff --git a/src/background/vmModules/ModuleManager.ts b/src/background/vmModules/ModuleManager.ts index dea7e43aa..3809686ac 100644 --- a/src/background/vmModules/ModuleManager.ts +++ b/src/background/vmModules/ModuleManager.ts @@ -1,5 +1,7 @@ import { Environment, Module } from '@avalabs/vm-module-types'; import { BitcoinModule } from '@avalabs/bitcoin-module'; +import { AvalancheModule } from '@avalabs/avalanche-module'; +import { EvmModule } from '@avalabs/evm-module'; import { ethErrors } from 'eth-rpc-errors'; import { singleton } from 'tsyringe'; @@ -7,11 +9,8 @@ import { assertPresent } from '@src/utils/assertions'; import { isDevelopment } from '@src/utils/environment'; import { NetworkWithCaipId } from '../services/network/models'; - import { VMModuleError } from './models'; -import { EvmModule } from '@avalabs/evm-module'; import { ApprovalController } from './ApprovalController'; -import { AvalancheModule } from '@avalabs/avalanche-module'; // https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md // Syntax for namespace is defined in CAIP-2 @@ -101,7 +100,6 @@ export class ModuleManager { }, }); } - return ( (await this.#getModuleByChainId(chainId)) ?? (await this.#getModuleByNamespace(namespace)) diff --git a/src/contexts/WalletProvider.tsx b/src/contexts/WalletProvider.tsx index 8e658a8fa..8a3bf4c3e 100644 --- a/src/contexts/WalletProvider.tsx +++ b/src/contexts/WalletProvider.tsx @@ -12,11 +12,7 @@ import { WalletDetails } from '@src/background/services/wallet/models'; import { WalletLocked } from '@src/pages/Wallet/WalletLocked'; import { ExtensionRequest } from '@src/background/connections/extensionConnection/models'; import { useLedgerContext } from './LedgerProvider'; -import { - PchainTxHistoryItem, - TxHistoryItem, - XchainTxHistoryItem, -} from '@src/background/services/history/models'; +import { TxHistoryItem } from '@src/background/services/history/models'; import { UnlockWalletHandler } from '@src/background/services/lock/handlers/unlockWalletState'; import { LockChangePasswordHandler } from '@src/background/services/lock/handlers/changeWalletPassword'; import { GetUnencryptedMnemonicHandler } from '@src/background/services/wallet/handlers/getUnencryptedMnemonic'; @@ -41,9 +37,7 @@ type WalletStateAndMethods = { ): Promise; getWallet(id: string): WalletDetails | undefined; getUnencryptedMnemonic(password: string): Promise; - getTransactionHistory(): Promise< - TxHistoryItem[] | PchainTxHistoryItem[] | XchainTxHistoryItem[] - >; + getTransactionHistory(): Promise; }; const WalletContext = createContext({ wallets: [], diff --git a/src/pages/Send/components/ContactSelect.tsx b/src/pages/Send/components/ContactSelect.tsx index cc27f7019..607eb3b69 100644 --- a/src/pages/Send/components/ContactSelect.tsx +++ b/src/pages/Send/components/ContactSelect.tsx @@ -27,6 +27,7 @@ import { stripAddressPrefix } from '@src/utils/stripAddressPrefix'; import { indexOf } from 'lodash'; import { isBitcoinNetwork } from '@src/background/services/network/utils/isBitcoinNetwork'; import { isXchainNetwork } from '@src/background/services/network/utils/isAvalancheXchainNetwork'; +import { ETHEREUM_ADDRESS } from '@src/utils/bridgeTransactionUtils'; interface ContactSelectProps { selectedContact?: Contact; @@ -81,21 +82,13 @@ export const ContactSelect = ({ // filter out dupe to addresses return ( index === self.findIndex((temp) => temp.to === tx.to) && - tx.to !== '0x0000000000000000000000000000000000000000' + tx.to !== ETHEREUM_ADDRESS ); }); const contactHistory = filteredHistory.reduce((acc, tx) => { - if (isTxHistoryItem(tx)) { - const identifiedContact = identifyAddress(tx.to); - if (indexOf(acc, identifiedContact) === -1) - acc.push(identifyAddress(tx.to)); - return acc; - } + const addressIdentities = [identifyAddress(tx.to)]; - const addressIdentities = tx.to.map((toAddress) => - identifyAddress(toAddress) - ); addressIdentities.forEach((identity) => { const addressToCheck = isBitcoinNetwork(network) ? identity.addressBTC diff --git a/src/pages/Wallet/WalletRecentTxs.tsx b/src/pages/Wallet/WalletRecentTxs.tsx index 05f372281..51a27e661 100644 --- a/src/pages/Wallet/WalletRecentTxs.tsx +++ b/src/pages/Wallet/WalletRecentTxs.tsx @@ -4,12 +4,7 @@ import { Scrollbars } from '@src/components/common/scrollbars/Scrollbars'; import { NoTransactions } from './components/NoTransactions'; import { isSameDay, endOfYesterday, endOfToday, format } from 'date-fns'; import { useNetworkContext } from '@src/contexts/NetworkProvider'; -import { - PchainTxHistoryItem, - TransactionType, - TxHistoryItem, - XchainTxHistoryItem, -} from '@src/background/services/history/models'; +import { TxHistoryItem } from '@src/background/services/history/models'; import { useAccountsContext } from '@src/contexts/AccountsProvider'; import { useTranslation } from 'react-i18next'; import { getExplorerAddressByNetwork } from '@src/utils/getExplorerAddress'; @@ -42,6 +37,7 @@ import { import { isXchainNetwork } from '@src/background/services/network/utils/isAvalancheXchainNetwork'; import { getAddressForChain } from '@src/utils/getAddressForChain'; import { XchainActivityCard } from './components/History/components/ActivityCard/XchainActivityCard'; +import { Transaction, TransactionType } from '@avalabs/vm-module-types'; type WalletRecentTxsProps = { isEmbedded?: boolean; @@ -121,7 +117,7 @@ export function WalletRecentTxs({ const yesterday = endOfYesterday(); const today = endOfToday(); const [unfilteredTxHistory, setUnfilteredTxHistory] = useState< - TxHistoryItem[] | PchainTxHistoryItem[] | XchainTxHistoryItem[] + TxHistoryItem[] >([]); const { network } = useNetworkContext(); @@ -192,9 +188,7 @@ export function WalletRecentTxs({ ); } - function shouldTxBeKept( - tx: TxHistoryItem | PchainTxHistoryItem | XchainTxHistoryItem - ) { + function shouldTxBeKept(tx: TxHistoryItem) { if (isTxHistoryItem(tx) && tx.isBridge && isPendingBridge(tx)) { return false; } @@ -219,19 +213,23 @@ export function WalletRecentTxs({ if (filter === FilterType.ALL) { return true; } else if (filter === FilterType.BRIDGE) { - return tx.isBridge; + return tx.txType === TransactionType.BRIDGE || tx.isBridge; } else if (filter === FilterType.SWAP) { - return tx.type === TransactionType.SWAP; + return tx.txType === TransactionType.SWAP; } else if (filter === FilterType.CONTRACT_CALL) { - return tx.isContractCall && tx.type !== TransactionType.SWAP; + return ( + tx.isContractCall && !tx.isBridge && tx.txType !== TransactionType.SWAP + ); } else if (filter === FilterType.INCOMING) { return tx.isIncoming; } else if (filter === FilterType.OUTGOING) { return tx.isOutgoing; } else if (filter === FilterType.NFTS) { return ( - tx.type === TransactionType.NFT_BUY || - (tx.type === TransactionType.TRANSFER && + tx.txType === TransactionType.NFT_BUY || + ((tx.txType === TransactionType.TRANSFER || + tx.txType === TransactionType.NFT_RECEIVE || + tx.txType === TransactionType.UNKNOWN) && tx.tokens[0] && isNftTokenType(tx.tokens[0].type)) ); @@ -241,7 +239,7 @@ export function WalletRecentTxs({ } function pchainTxHistoryItemFilter( - tx: PchainTxHistoryItem, + tx: TxHistoryItem, filter: FilterType | PchainFilterType | XchainFilterType ) { if (filter === PchainFilterType.ALL) { @@ -256,14 +254,14 @@ export function WalletRecentTxs({ const typeBasedOnFilter = PchainFilterTxTypeMap[filter]; if (typeBasedOnFilter) { - return tx.type === typeBasedOnFilter; + return tx.txType === typeBasedOnFilter; } return false; } function xchainTxHistoryItemFilter( - tx: XchainTxHistoryItem, + tx: Transaction, filter: FilterType | PchainFilterType | XchainFilterType ) { if (filter === XchainFilterType.ALL) { @@ -278,7 +276,7 @@ export function WalletRecentTxs({ const typeBasedOnFilter = XchainFilterTxTypeMap[filter]; if (typeBasedOnFilter) { - return tx.type === typeBasedOnFilter; + return tx.txType === typeBasedOnFilter; } return false; @@ -286,7 +284,7 @@ export function WalletRecentTxs({ const filteredTxHistory = useMemo(() => { function shouldTxBeKept( - tx: TxHistoryItem | PchainTxHistoryItem | XchainTxHistoryItem, + tx: TxHistoryItem, filter: FilterType | PchainFilterType | XchainFilterType ) { if (isTxHistoryItem(tx)) { diff --git a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCard.tsx b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCard.tsx index b229f9811..d7f7b6563 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCard.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCard.tsx @@ -12,10 +12,7 @@ import { } from '@avalabs/core-k2-components'; import { weiToAvax } from '@avalabs/core-utils-sdk'; import { isNftTokenType } from '@src/background/services/balances/nft/utils/isNFT'; -import { - TransactionType, - TxHistoryItem, -} from '@src/background/services/history/models'; +import { TxHistoryItem } from '@src/background/services/history/models'; import { isBitcoinNetwork } from '@src/background/services/network/utils/isBitcoinNetwork'; import { useAnalyticsContext } from '@src/contexts/AnalyticsProvider'; import { useNetworkContext } from '@src/contexts/NetworkProvider'; @@ -26,6 +23,7 @@ import { ActivityCardAmount } from './ActivityCardAmount'; import { ActivityCardDetails } from './ActivityCardDetails'; import { ActivityCardIcon } from './ActivityCardIcon'; import { ActivityCardSummary } from './ActivityCardSummary'; +import { TransactionType } from '@avalabs/vm-module-types'; export interface ActivityCardProp { historyItem: TxHistoryItem; @@ -41,9 +39,14 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { const showDetailsOption = useMemo(() => { if ( - historyItem.type === TransactionType.SWAP || - historyItem.type === TransactionType.NFT_BUY || - (historyItem.type === TransactionType.TRANSFER && + historyItem.txType === TransactionType.SWAP || + historyItem.txType === TransactionType.NFT_BUY || + historyItem.txType === TransactionType.NFT_RECEIVE || + historyItem.txType === TransactionType.NFT_SEND || + (historyItem.txType === TransactionType.SEND && + historyItem.tokens[0]?.type === 'ERC1155') || + ((historyItem.txType === TransactionType.TRANSFER || + historyItem.txType === TransactionType.UNKNOWN) && historyItem.tokens[0] && isNftTokenType(historyItem.tokens[0].type)) ) { @@ -80,7 +83,16 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { const txTitle = useMemo(() => { if (network) { - switch (historyItem.type) { + if (historyItem.isBridge) { + return t('Bridge'); + } + if ( + historyItem.txType === TransactionType.SEND && + historyItem.tokens[0]?.type === 'ERC1155' + ) { + return t('NFT Sent'); + } + switch (historyItem.txType) { case TransactionType.BRIDGE: return t('Bridge'); case TransactionType.SWAP: @@ -91,7 +103,11 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { return t('Received'); case TransactionType.NFT_BUY: return t('NFT Buy'); + case TransactionType.NFT_SEND: + return t('NFT Sent'); case TransactionType.TRANSFER: + case TransactionType.UNKNOWN: + case TransactionType.NFT_RECEIVE: if ( historyItem.tokens[0] && isNftTokenType(historyItem.tokens[0]?.type) @@ -100,6 +116,7 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { } else { return t('Transfer'); } + default: if (historyItem.isContractCall) { return t('Contract Call'); @@ -114,7 +131,7 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { data-testid={ historyItem.isContractCall ? 'Contract-call-activity-card' - : historyItem.type + '-activity-card' + : historyItem.txType + '-activity-card' } sx={{ p: 2, backgroundImage: 'none' }} > @@ -141,7 +158,7 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { // We only want to know when it's being shown capture('ActivityCardDetailShown', { chainId: network?.chainId, - type: historyItem.type, + type: historyItem.txType, }); } setShowDetails(isToggledOnNow); @@ -198,7 +215,7 @@ export function ActivityCard({ historyItem }: ActivityCardProp) { onClick={async () => { await capture('ActivityCardLinkClicked', { chainId: network?.chainId, - type: historyItem.type, + type: historyItem.txType, }); window.open(historyItem.explorerLink, '_blank', 'noreferrer'); }} diff --git a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardAmount.tsx b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardAmount.tsx index f1115ac70..791dc1da3 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardAmount.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardAmount.tsx @@ -1,11 +1,9 @@ import { Stack, Typography } from '@avalabs/core-k2-components'; import { isNftTokenType } from '@src/background/services/balances/nft/utils/isNFT'; -import { - TransactionType, - TxHistoryItem, -} from '@src/background/services/history/models'; +import { TxHistoryItem } from '@src/background/services/history/models'; import { useAccountsContext } from '@src/contexts/AccountsProvider'; import { ActivityCardProp } from './ActivityCard'; +import { TransactionType } from '@avalabs/vm-module-types'; export function ActivityCardAmount({ historyItem }: ActivityCardProp) { const { @@ -28,7 +26,7 @@ export function ActivityCardAmount({ historyItem }: ActivityCardProp) { return tx.tokens.find((token) => token.to?.address === userAddress); } - if (historyItem.type === TransactionType.SWAP && activeAccount?.addressC) { + if (historyItem.txType === TransactionType.SWAP && activeAccount?.addressC) { const source = getSourceToken(historyItem); const target = getTargetToken(historyItem); return ( diff --git a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardDetails.tsx b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardDetails.tsx index a7d416bd4..94ba81ce3 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardDetails.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardDetails.tsx @@ -6,16 +6,16 @@ import { useTheme, } from '@avalabs/core-k2-components'; import { isNftTokenType } from '@src/background/services/balances/nft/utils/isNFT'; -import { TransactionType } from '@src/background/services/history/models'; import { TokenIcon } from '@src/components/common/TokenIcon'; import { useTranslation } from 'react-i18next'; import { ActivityCardProp } from './ActivityCard'; +import { TransactionType } from '@avalabs/vm-module-types'; export function ActivityCardDetails({ historyItem }: ActivityCardProp) { const theme = useTheme(); const { t } = useTranslation(); - if (historyItem.type === TransactionType.SWAP) { + if (historyItem.txType === TransactionType.SWAP) { return ( {historyItem.tokens.map((token, i) => { @@ -51,10 +51,15 @@ export function ActivityCardDetails({ historyItem }: ActivityCardProp) { ); } else if ( - (historyItem.type === TransactionType.TRANSFER && + ((historyItem.txType === TransactionType.TRANSFER || + historyItem.txType === TransactionType.UNKNOWN) && historyItem.tokens[0]?.type && isNftTokenType(historyItem.tokens[0].type)) || - historyItem.type === TransactionType.NFT_BUY + historyItem.txType === TransactionType.NFT_BUY || + historyItem.txType === TransactionType.NFT_RECEIVE || + historyItem.txType === TransactionType.NFT_SEND || + (historyItem.txType === TransactionType.SEND && + historyItem.tokens[0]?.type === 'ERC1155') ) { return ( ); - switch (historyItem.type) { + if (historyItem.isBridge) { + setTxIcon(); + return; + } + + if ( + historyItem.txType === TransactionType.SEND && + historyItem.tokens[0]?.type === 'ERC1155' + ) { + setTxIcon(getNftIcon(historyItem)); + return; + } + switch (historyItem.txType) { case TransactionType.BRIDGE: setTxIcon(); break; @@ -64,9 +74,13 @@ export function ActivityCardIcon({ historyItem }: ActivityCardProp) { setTxIcon(); break; case TransactionType.NFT_BUY: + case TransactionType.NFT_SEND: + case TransactionType.NFT_RECEIVE: setTxIcon(getNftIcon(historyItem)); break; + case TransactionType.TRANSFER: + case TransactionType.UNKNOWN: if ( historyItem.tokens[0] && isNftTokenType(historyItem.tokens[0]?.type) @@ -76,11 +90,13 @@ export function ActivityCardIcon({ historyItem }: ActivityCardProp) { setTxIcon(defaultIcon); } break; + default: if (historyItem.isContractCall) { setTxIcon(); break; } + setTxIcon(defaultIcon); } }, [t, theme, historyItem]); diff --git a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardSummary.tsx b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardSummary.tsx index 9e2e08e67..d472ec344 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardSummary.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/ActivityCardSummary.tsx @@ -1,10 +1,10 @@ import { Typography } from '@avalabs/core-k2-components'; -import { TransactionType } from '@src/background/services/history/models'; import { useAccountsContext } from '@src/contexts/AccountsProvider'; import { truncateAddress } from '@src/utils/truncateAddress'; import { useTranslation } from 'react-i18next'; import { useBlockchainNames } from '../../useBlockchainNames'; import { ActivityCardProp } from './ActivityCard'; +import { TransactionType } from '@avalabs/vm-module-types'; export function ActivityCardSummary({ historyItem }: ActivityCardProp) { const { @@ -14,7 +14,7 @@ export function ActivityCardSummary({ historyItem }: ActivityCardProp) { useBlockchainNames(historyItem); const { t } = useTranslation(); - if (historyItem.type === TransactionType.BRIDGE) { + if (historyItem.txType === TransactionType.BRIDGE || historyItem.isBridge) { return ( ); - } else if (historyItem.type === TransactionType.SWAP) { + } else if (historyItem.txType === TransactionType.SWAP) { const sourceToken = historyItem.tokens.find( (token) => token.from?.address === activeAccount?.addressC ); diff --git a/src/pages/Wallet/components/History/components/ActivityCard/PchainActivityCard.tsx b/src/pages/Wallet/components/History/components/ActivityCard/PchainActivityCard.tsx index 3c39ff466..9c94e3ab9 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/PchainActivityCard.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/PchainActivityCard.tsx @@ -7,7 +7,6 @@ import { Typography, useTheme, } from '@avalabs/core-k2-components'; -import { PchainTxHistoryItem } from '@src/background/services/history/models'; import { PrimaryNetworkMethodIcon } from './PrimaryNetworkMethodIcon'; import { useNetworkContext } from '@src/contexts/NetworkProvider'; import { PChainTransactionType } from '@avalabs/glacier-sdk'; @@ -15,9 +14,10 @@ import { useTranslation } from 'react-i18next'; import { useMemo } from 'react'; import { useAnalyticsContext } from '@src/contexts/AnalyticsProvider'; import { truncateAddress } from '@avalabs/core-utils-sdk'; +import { Transaction } from '@avalabs/vm-module-types'; export interface PchainActivityCardProp { - historyItem: PchainTxHistoryItem; + historyItem: Transaction; } export function PchainActivityCard({ historyItem }: PchainActivityCardProp) { @@ -28,7 +28,7 @@ export function PchainActivityCard({ historyItem }: PchainActivityCardProp) { const txTitle = useMemo(() => { if (network) { - switch (historyItem.type) { + switch (historyItem.txType) { case PChainTransactionType.ADD_DELEGATOR_TX: return t('Add Delegator'); case PChainTransactionType.ADD_SUBNET_VALIDATOR_TX: @@ -72,7 +72,7 @@ export function PchainActivityCard({ historyItem }: PchainActivityCardProp) { return ( - + {historyItem.txType && ( + + )} - {historyItem.token.amount} + {historyItem.tokens[0]?.amount} - {historyItem.token.symbol} + {historyItem.tokens[0]?.symbol} @@ -140,15 +142,26 @@ export function PchainActivityCard({ historyItem }: PchainActivityCardProp) { {txDirection}: - {Array.from(txAddressesToShow).map((address, i) => ( + {Array.isArray(txAddressesToShow) && + Array.from(txAddressesToShow).map((address, i) => ( + + {truncateAddress(address)} + + ))} + + {!Array.isArray(txAddressesToShow) && ( - {truncateAddress(address)} + {truncateAddress(txAddressesToShow)} - ))} + )} @@ -174,7 +187,7 @@ export function PchainActivityCard({ historyItem }: PchainActivityCardProp) { onClick={async () => { capture('PchainActivityCardLinkClicked', { chainId: network?.chainId, - type: historyItem.type, + type: historyItem.txType, }); window.open(historyItem.explorerLink, '_blank', 'noreferrer'); }} diff --git a/src/pages/Wallet/components/History/components/ActivityCard/PrimaryNetworkMethodIcon.tsx b/src/pages/Wallet/components/History/components/ActivityCard/PrimaryNetworkMethodIcon.tsx index 8c126acd8..07bfb7c54 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/PrimaryNetworkMethodIcon.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/PrimaryNetworkMethodIcon.tsx @@ -17,14 +17,15 @@ import { PChainTransactionType, XChainTransactionType, } from '@avalabs/glacier-sdk'; +import { TransactionType } from '@avalabs/vm-module-types'; export interface PrimaryNetworkMethodIconProp { methodName: + | TransactionType | PChainTransactionType | XChainTransactionType | 'CreateAssetTx' | 'OperationTx'; } - const METHOD_NAME_TO_ICON: Record< | PChainTransactionType | XChainTransactionType diff --git a/src/pages/Wallet/components/History/components/ActivityCard/XchainActivityCard.tsx b/src/pages/Wallet/components/History/components/ActivityCard/XchainActivityCard.tsx index 37d33f4e3..2c837c96f 100644 --- a/src/pages/Wallet/components/History/components/ActivityCard/XchainActivityCard.tsx +++ b/src/pages/Wallet/components/History/components/ActivityCard/XchainActivityCard.tsx @@ -7,7 +7,6 @@ import { Typography, useTheme, } from '@avalabs/core-k2-components'; -import { XchainTxHistoryItem } from '@src/background/services/history/models'; import { PrimaryNetworkMethodIcon } from './PrimaryNetworkMethodIcon'; import { useNetworkContext } from '@src/contexts/NetworkProvider'; import { XChainTransactionType } from '@avalabs/glacier-sdk'; @@ -15,9 +14,10 @@ import { useTranslation } from 'react-i18next'; import { useMemo } from 'react'; import { useAnalyticsContext } from '@src/contexts/AnalyticsProvider'; import { truncateAddress } from '@avalabs/core-utils-sdk'; +import { Transaction } from '@avalabs/vm-module-types'; export interface XchainActivityCardProp { - historyItem: XchainTxHistoryItem; + historyItem: Transaction; } export function XchainActivityCard({ historyItem }: XchainActivityCardProp) { @@ -28,7 +28,7 @@ export function XchainActivityCard({ historyItem }: XchainActivityCardProp) { const txTitle = useMemo(() => { if (network) { - switch (historyItem.type) { + switch (historyItem.txType) { case XChainTransactionType.BASE_TX: return t('BaseTx'); case XChainTransactionType.CREATE_ASSET_TX: @@ -52,7 +52,7 @@ export function XchainActivityCard({ historyItem }: XchainActivityCardProp) { return ( - + {historyItem.txType && ( + + )} - {historyItem.token.amount} + {historyItem.tokens[0]?.amount} - {historyItem.token.symbol} + {historyItem.tokens[0]?.symbol} @@ -120,15 +122,26 @@ export function XchainActivityCard({ historyItem }: XchainActivityCardProp) { {txDirection}: - {Array.from(txAddressesToShow).map((address, i) => ( + {Array.isArray(txAddressesToShow) && + Array.from(txAddressesToShow).map((address, i) => ( + + {truncateAddress(address)} + + ))} + + {!Array.isArray(txAddressesToShow) && ( - {truncateAddress(address)} + {truncateAddress(txAddressesToShow)} - ))} + )} @@ -154,7 +167,7 @@ export function XchainActivityCard({ historyItem }: XchainActivityCardProp) { onClick={async () => { capture('XchainActivityCardLinkClicked', { chainId: network?.chainId, - type: historyItem.type, + type: historyItem.txType, }); window.open(historyItem.explorerLink, '_blank', 'noreferrer'); }}