diff --git a/packages/shared/src/lib/core/activity/stores/all-account-activities.store.ts b/packages/shared/src/lib/core/activity/stores/all-account-activities.store.ts index 7a0276fc91..ed8c944257 100644 --- a/packages/shared/src/lib/core/activity/stores/all-account-activities.store.ts +++ b/packages/shared/src/lib/core/activity/stores/all-account-activities.store.ts @@ -61,7 +61,7 @@ export function updateActivityByTransactionId( export function updateActivityByActivityId( accountIndex: number, activityId: string, - partialBaseActivity: Partial + partialBaseActivity: Partial ): void { allAccountActivities.update((state) => { const activity = state[accountIndex]?.find((_activity) => _activity.id === activityId) diff --git a/packages/shared/src/lib/core/activity/utils/evm/generateBaseEvmActivity.ts b/packages/shared/src/lib/core/activity/utils/evm/generateBaseEvmActivity.ts index 1a7d41cee3..ce278bd327 100644 --- a/packages/shared/src/lib/core/activity/utils/evm/generateBaseEvmActivity.ts +++ b/packages/shared/src/lib/core/activity/utils/evm/generateBaseEvmActivity.ts @@ -25,7 +25,9 @@ export async function generateBaseEvmActivity( const subject = direction === ActivityDirection.Outgoing ? recipient : sender const isInternal = isSubjectInternal(recipient) - const timestamp = transaction.timestamp ?? (await getTimeStamp(transaction.blockNumber, evmNetwork)) + const timestamp = transaction.blockNumber + ? await getTimeStamp(transaction.blockNumber, evmNetwork) + : transaction.timestamp // For native token transfers on L2, gasUsed is 0. Therefor we fallback to the estimatedGas // https://discord.com/channels/397872799483428865/930642258427019354/1168854453005332490 diff --git a/packages/shared/src/lib/core/network/classes/evm-network.class.ts b/packages/shared/src/lib/core/network/classes/evm-network.class.ts index 3cad4a52b5..9adbfa5daf 100644 --- a/packages/shared/src/lib/core/network/classes/evm-network.class.ts +++ b/packages/shared/src/lib/core/network/classes/evm-network.class.ts @@ -1,10 +1,6 @@ import { getAddressFromAccountForNetwork, IAccountState } from '@core/account' import { IError } from '@core/error/interfaces' -import { - AVERAGE_BLOCK_TIME_IN_SECONDS, - ETHEREUM_CONFIRMATION_THRESHOLD, - NETWORK_STATUS_POLL_INTERVAL, -} from '@core/network/constants' +import { NETWORK_STATUS_POLL_INTERVAL } from '@core/network/constants' import { getPersistedErc721NftsForNetwork, updateErc721NftsOwnership } from '@core/nfts/actions' import { Nft } from '@core/nfts/interfaces' import { buildNftFromPersistedErc721Nft } from '@core/nfts/utils' @@ -40,8 +36,8 @@ export class EvmNetwork implements IEvmNetwork { public readonly novesIndexerUrl: string | undefined public readonly rpcEndpoint: string public readonly type: EvmNetworkType = NetworkType.Evm - public readonly averageBlockTimeInSeconds: number = AVERAGE_BLOCK_TIME_IN_SECONDS - public readonly blocksUntilConfirmed: number = ETHEREUM_CONFIRMATION_THRESHOLD + public averageBlockTimeInSeconds: number | undefined + public readonly blocksUntilConfirmed: number public health: Writable = writable(NetworkHealth.Operational) public statusPoll: number | undefined @@ -59,6 +55,7 @@ export class EvmNetwork implements IEvmNetwork { blockscoutIndexerUrl, novesIndexerUrl, rpcEndpoint, + blocksUntilConfirmed, }: IBaseEvmNetworkConfiguration) { try { const _rpcEndpoint = new URL(rpcEndpoint).href @@ -74,6 +71,8 @@ export class EvmNetwork implements IEvmNetwork { this.blockscoutIndexerUrl = blockscoutIndexerUrl this.novesIndexerUrl = novesIndexerUrl this.rpcEndpoint = _rpcEndpoint + this.blocksUntilConfirmed = blocksUntilConfirmed + void this.setAverageBlockTime(3) void this.startStatusPoll() } catch (err) { @@ -82,6 +81,37 @@ export class EvmNetwork implements IEvmNetwork { } } + private async setAverageBlockTime(blockCount = 10): Promise { + this.averageBlockTimeInSeconds = await this.getAverageBlockTime(blockCount) + } + + private async getAverageBlockTime(blockCount = 10): Promise { + try { + const latestBlockNumber = await this.provider.eth.getBlockNumber() + const blocks: { timestamp: bigint }[] = [] + + // Fetch the latest `blockCount` blocks + for (let i = 0; i < blockCount; i++) { + const block = await this.provider.eth.getBlock(Number(latestBlockNumber - BigInt(i))) + blocks.push(block) + } + + // Calculate the time differences between consecutive blocks + let totalTime = BigInt(0) + for (let i = 1; i < blocks.length; i++) { + const timeDiff = blocks[i - 1].timestamp - blocks[i].timestamp + totalTime += timeDiff + } + + // Calculate the average time difference + const averageBlockTime = totalTime / BigInt(blocks.length - 1) + + return Number(averageBlockTime) + } catch (error) { + console.error('Error calculating average block time:', error) + } + } + async getNftsForAccount(account: IAccountState): Promise { // ERC721 NFTs const evmAddress = getAddressFromAccountForNetwork(account, this.id) diff --git a/packages/shared/src/lib/core/network/classes/isc-chain.class.ts b/packages/shared/src/lib/core/network/classes/isc-chain.class.ts index 35679fcd2a..94e44ecf42 100644 --- a/packages/shared/src/lib/core/network/classes/isc-chain.class.ts +++ b/packages/shared/src/lib/core/network/classes/isc-chain.class.ts @@ -15,7 +15,6 @@ import { Converter } from '@core/utils' import { BigIntLike } from '@ethereumjs/util' import { IIscChain, IIscChainConfiguration, IIscChainMetadata } from '../interfaces' import { NftStandard } from '@core/nfts/enums' -import { ISC_CONFIRMATION_THRESHOLD } from '@core/network/constants' import { IExplorerConfig } from '@auxiliary/explorer' export class IscChain extends EvmNetwork implements IIscChain { @@ -28,7 +27,6 @@ export class IscChain extends EvmNetwork implements IIscChain { public readonly apiEndpoint: string public readonly aliasAddress: string public readonly type = NetworkType.Isc - public readonly blocksUntilConfirmed = ISC_CONFIRMATION_THRESHOLD constructor(chainConfiguration: IIscChainConfiguration) { const { rpcEndpoint, aliasAddress, apiEndpoint } = chainConfiguration diff --git a/packages/shared/src/lib/core/network/constants/average-block-time-in-seconds.constant.ts b/packages/shared/src/lib/core/network/constants/average-block-time-in-seconds.constant.ts deleted file mode 100644 index 5ca9412334..0000000000 --- a/packages/shared/src/lib/core/network/constants/average-block-time-in-seconds.constant.ts +++ /dev/null @@ -1 +0,0 @@ -export const AVERAGE_BLOCK_TIME_IN_SECONDS = 12 diff --git a/packages/shared/src/lib/core/network/constants/confirmation-threshold.constant.ts b/packages/shared/src/lib/core/network/constants/confirmation-threshold.constant.ts deleted file mode 100644 index 08fb97dca2..0000000000 --- a/packages/shared/src/lib/core/network/constants/confirmation-threshold.constant.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ETHEREUM_CONFIRMATION_THRESHOLD = 6 -export const ISC_CONFIRMATION_THRESHOLD = 0 - -export const FAILED_CONFIRMATION = -1 diff --git a/packages/shared/src/lib/core/network/constants/default-evm-network-configurations-for-stardust-network.constant.ts b/packages/shared/src/lib/core/network/constants/default-evm-network-configurations-for-stardust-network.constant.ts index bdfa5a81b3..4b4a0d2693 100644 --- a/packages/shared/src/lib/core/network/constants/default-evm-network-configurations-for-stardust-network.constant.ts +++ b/packages/shared/src/lib/core/network/constants/default-evm-network-configurations-for-stardust-network.constant.ts @@ -26,6 +26,7 @@ const ETHEREUM_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Ethereum], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Ethereum], rpcEndpoint: 'https://ethereum-rpc.publicnode.com', + blocksUntilConfirmed: 6, } // Ethereum L2s @@ -41,6 +42,7 @@ export const ARBITRUM_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Arbitrum], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Arbitrum], rpcEndpoint: 'https://arbitrum-one-rpc.publicnode.com', + blocksUntilConfirmed: 6, } export const BASE_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -54,6 +56,7 @@ export const BASE_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Base], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Base], rpcEndpoint: 'https://base-rpc.publicnode.com', + blocksUntilConfirmed: 6, } export const BLAST_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -67,6 +70,7 @@ export const BLAST_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Blast], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Blast], rpcEndpoint: 'https://blast-rpc.publicnode.com', + blocksUntilConfirmed: 6, } export const OPTIMISM_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -80,6 +84,7 @@ export const OPTIMISM_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Optimism], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Optimism], rpcEndpoint: 'https://optimism-rpc.publicnode.com', + blocksUntilConfirmed: 6, } export const IMMUTABLE_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -93,6 +98,7 @@ export const IMMUTABLE_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Immutable], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Immutable], rpcEndpoint: 'https://rpc.immutable.com', + blocksUntilConfirmed: 6, } // BNB Mainnet @@ -107,6 +113,7 @@ export const BNB_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { coinType: DEFAULT_COIN_TYPE[SupportedNetworkId.Bnb] ?? 0, explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Bnb], rpcEndpoint: 'https://bsc-dataseed1.binance.org/', + blocksUntilConfirmed: 3, } // Ethereum Sepolia Testnet @@ -122,6 +129,7 @@ export const SEPOLIA_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.Sepolia], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.Sepolia], rpcEndpoint: 'https://ethereum-sepolia-rpc.publicnode.com', + blocksUntilConfirmed: 2, } // Ethereum L2 Testnets @@ -137,6 +145,7 @@ export const ARBITRUM_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguratio explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.ArbitrumSepoliaTestnet], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.ArbitrumSepoliaTestnet], rpcEndpoint: 'https://sepolia-rollup.arbitrum.io/rpc', + blocksUntilConfirmed: 2, } export const BASE_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -150,6 +159,7 @@ export const BASE_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.BaseSepoliaTestnet], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.BaseSepoliaTestnet], rpcEndpoint: 'https://sepolia.base.org', + blocksUntilConfirmed: 2, } export const BLAST_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -163,6 +173,7 @@ export const BLAST_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.BlastSepoliaTestnet], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.BlastSepoliaTestnet], rpcEndpoint: 'https://sepolia.blast.io', + blocksUntilConfirmed: 2, } export const OPTIMISM_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -176,6 +187,7 @@ export const OPTIMISM_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguratio explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.OptimismSepoliaTestnet], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.OptimismSepoliaTestnet], rpcEndpoint: 'https://sepolia.optimism.io', + blocksUntilConfirmed: 2, } export const IMMUTABLE_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { @@ -189,6 +201,7 @@ export const IMMUTABLE_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfigurati explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.ImmutableTestnet], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.ImmutableTestnet], rpcEndpoint: 'https://rpc.testnet.immutable.com', + blocksUntilConfirmed: 2, } // BNB Testnets @@ -203,6 +216,7 @@ export const BNB_TESTNET_NETWORK_CONFIGURATION: IPureEvmNetworkConfiguration = { coinType: DEFAULT_COIN_TYPE[SupportedNetworkId.BnbTestnet] ?? 0, explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.BnbTestnet], rpcEndpoint: 'https://data-seed-prebsc-1-s1.binance.org:8545', + blocksUntilConfirmed: 1, } export const KNOWN_EVM_MAINNET_NETWORKS_CONFIGURATIONS: Readonly = [ @@ -229,6 +243,26 @@ export const KNOWN_EVM_NETWORKS_CONFIGURATIONS: Readonly = { + [SupportedL1EvmNetworkId.Ethereum]: ETHEREUM_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.Arbitrum]: ARBITRUM_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.Base]: BASE_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.Blast]: BLAST_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.Optimism]: OPTIMISM_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.Immutable]: IMMUTABLE_NETWORK_CONFIGURATION, + [SupportedL1EvmNetworkId.Bnb]: BNB_NETWORK_CONFIGURATION, + [SupportedL1EvmNetworkId.BnbTestnet]: BNB_TESTNET_NETWORK_CONFIGURATION, + [SupportedL1EvmNetworkId.Sepolia]: SEPOLIA_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.ArbitrumSepoliaTestnet]: ARBITRUM_TESTNET_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.BaseSepoliaTestnet]: BASE_TESTNET_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.BlastSepoliaTestnet]: BLAST_TESTNET_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.OptimismSepoliaTestnet]: OPTIMISM_TESTNET_NETWORK_CONFIGURATION, + [SupportedL2EvmNetworkId.ImmutableTestnet]: IMMUTABLE_TESTNET_NETWORK_CONFIGURATION, +} export const DEFAULT_EVM_NETWORK_CONFIGURATIONS_FOR_STARDUST_NETWORK: Readonly<{ [key in StardustNetworkId]: IPureEvmNetworkConfiguration[] diff --git a/packages/shared/src/lib/core/network/constants/default-isc-chains-configurations.constant.ts b/packages/shared/src/lib/core/network/constants/default-isc-chains-configurations.constant.ts index 2c0d8be4a1..e9868194e6 100644 --- a/packages/shared/src/lib/core/network/constants/default-isc-chains-configurations.constant.ts +++ b/packages/shared/src/lib/core/network/constants/default-isc-chains-configurations.constant.ts @@ -21,6 +21,7 @@ const IOTA_EVM_CHAIN_CONFIGURATION: IIscChainConfiguration = { apiEndpoint: 'https://api.evm.iotaledger.net/', explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.IotaEvm], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.IotaEvm], + blocksUntilConfirmed: 0, } const SHIMMER_EVM_CHAIN_CONFIGURATION: IIscChainConfiguration = { @@ -36,6 +37,7 @@ const SHIMMER_EVM_CHAIN_CONFIGURATION: IIscChainConfiguration = { apiEndpoint: 'https://api.evm.shimmer.network/', explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.ShimmerEvm], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.ShimmerEvm], + blocksUntilConfirmed: 0, } const IOTA_TESTNET_EVM_CHAIN_CONFIGURATION: IIscChainConfiguration = { @@ -51,6 +53,7 @@ const IOTA_TESTNET_EVM_CHAIN_CONFIGURATION: IIscChainConfiguration = { apiEndpoint: 'https://api.evm.testnet.iotaledger.net/', explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.IotaTestnetEvm], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.IotaTestnetEvm], + blocksUntilConfirmed: 0, } // exported as used in tests @@ -67,6 +70,7 @@ export const TESTNET_EVM_CHAIN_CONFIGURATION: IIscChainConfiguration = { apiEndpoint: 'https://api.evm.testnet.shimmer.network/', explorer: DEFAULT_EXPLORER_CONFIGS[SupportedNetworkId.TestnetEvm], blockscoutIndexerUrl: DEFAULT_BLOCKSCOUT_INDEXER_URLS[SupportedNetworkId.TestnetEvm], + blocksUntilConfirmed: 0, } export const DEFAULT_ISC_CHAINS_CONFIGURATIONS: Readonly<{ [id in StardustNetworkId]?: IIscChainConfiguration }> = { diff --git a/packages/shared/src/lib/core/network/constants/failed-confirmation.constant.ts b/packages/shared/src/lib/core/network/constants/failed-confirmation.constant.ts new file mode 100644 index 0000000000..9f45af1c2c --- /dev/null +++ b/packages/shared/src/lib/core/network/constants/failed-confirmation.constant.ts @@ -0,0 +1 @@ +export const FAILED_CONFIRMATION = -1 diff --git a/packages/shared/src/lib/core/network/constants/index.ts b/packages/shared/src/lib/core/network/constants/index.ts index 90f511376f..e571667414 100644 --- a/packages/shared/src/lib/core/network/constants/index.ts +++ b/packages/shared/src/lib/core/network/constants/index.ts @@ -2,9 +2,8 @@ * CAUTION: If this file is exported in alphabetical order, it will * break the dependency flow. It MUST be exported first! */ -export * from './average-block-time-in-seconds.constant' export * from './base-token-supply-for-network.constant' -export * from './confirmation-threshold.constant' +export * from './failed-confirmation.constant' export * from './default-coin-type.constant' export * from './default-bech32-hrp.constant' export * from './default-blockscout-indexer-urls.constant' diff --git a/packages/shared/src/lib/core/network/interfaces/evm-network-configuration.interface.ts b/packages/shared/src/lib/core/network/interfaces/evm-network-configuration.interface.ts index 0c9dc1a70d..33666a59df 100644 --- a/packages/shared/src/lib/core/network/interfaces/evm-network-configuration.interface.ts +++ b/packages/shared/src/lib/core/network/interfaces/evm-network-configuration.interface.ts @@ -26,4 +26,5 @@ export interface IBaseEvmNetworkConfiguration { blockscoutIndexerUrl?: string novesIndexerUrl?: string rpcEndpoint: string + blocksUntilConfirmed: number } diff --git a/packages/shared/src/lib/core/network/interfaces/evm-network.interface.ts b/packages/shared/src/lib/core/network/interfaces/evm-network.interface.ts index 3bd8888cd7..cebd76ef6d 100644 --- a/packages/shared/src/lib/core/network/interfaces/evm-network.interface.ts +++ b/packages/shared/src/lib/core/network/interfaces/evm-network.interface.ts @@ -31,7 +31,7 @@ export interface IEvmNetwork extends IBaseNetwork, IBaseNetworkMetadata { provider: Web3Provider - averageBlockTimeInSeconds: number + averageBlockTimeInSeconds: number | undefined blocksUntilConfirmed: number blockscoutIndexerUrl?: string diff --git a/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts b/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts index 19db41a8e9..772700d1b6 100644 --- a/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts +++ b/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts @@ -1,7 +1,7 @@ import { AppStage } from '@core/app/enums' export const PROFILE_VERSION: Record = { - [AppStage.ALPHA]: 26, + [AppStage.ALPHA]: 27, [AppStage.BETA]: 1, [AppStage.PROD]: 15, } diff --git a/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-26-to-27.ts b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-26-to-27.ts new file mode 100644 index 0000000000..0263b36ed1 --- /dev/null +++ b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-26-to-27.ts @@ -0,0 +1,21 @@ +import { DEFAULT_EVM_NETWORK_CONFIGURATION, DEFAULT_ISC_CHAINS_CONFIGURATIONS, StardustNetworkId } from '@core/network' +import { IPersistedProfile } from '@core/profile/interfaces' + +export function alphaProfileMigration26To27(existingProfile: unknown): Promise { + const profile = existingProfile as IPersistedProfile & { + network: { + id: StardustNetworkId + } + } + + profile.network.chainConfigurations.forEach((chainConfiguration) => { + chainConfiguration.blocksUntilConfirmed = + DEFAULT_ISC_CHAINS_CONFIGURATIONS[profile.network.id]?.blocksUntilConfirmed ?? 0 + }) + + profile.evmNetworks.forEach((evmNetwork) => { + evmNetwork.blocksUntilConfirmed = DEFAULT_EVM_NETWORK_CONFIGURATION[evmNetwork.id]?.blocksUntilConfirmed ?? 0 + }) + + return Promise.resolve() +} diff --git a/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts index f17eb49076..577b84a6a8 100644 --- a/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts +++ b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts @@ -18,6 +18,7 @@ import { alphaProfileMigration22To23 } from './alpha-profile-migration-22-to-23' import { alphaProfileMigration23To24 } from './alpha-profile-migration-23-to-24' import { alphaProfileMigration24To25 } from './alpha-profile-migration-24-to-25' import { alphaProfileMigration25To26 } from './alpha-profile-migration-25-to-26' +import { alphaProfileMigration26To27 } from './alpha-profile-migration-26-to-27' import { alphaProfileMigration3To4 } from './alpha-profile-migration-3-to-4' import { alphaProfileMigration4To5 } from './alpha-profile-migration-4-to-5' import { alphaProfileMigration5To6 } from './alpha-profile-migration-5-to-6' @@ -59,4 +60,5 @@ export const ALPHA_PROFILE_MIGRATION_MAP: ProfileMigrationMap = { // ^^^ release 1.1.3 ^^^ 24: alphaProfileMigration24To25, 25: alphaProfileMigration25To26, + 26: alphaProfileMigration26To27, } diff --git a/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-14-to-15.ts b/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-14-to-15.ts index 0f56bdeb99..199c55ceab 100644 --- a/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-14-to-15.ts +++ b/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-14-to-15.ts @@ -1,10 +1,16 @@ -import { IIscChainConfiguration, IPureEvmNetworkConfiguration } from '@core/network' -import { DEFAULT_BLOCKSCOUT_INDEXER_URLS, DEFAULT_EXPLORER_CONFIGS } from '@core/network/constants' +import { IIscChainConfiguration, IPureEvmNetworkConfiguration, StardustNetworkId } from '@core/network' +import { + DEFAULT_BLOCKSCOUT_INDEXER_URLS, + DEFAULT_EVM_NETWORK_CONFIGURATION, + DEFAULT_EXPLORER_CONFIGS, + DEFAULT_ISC_CHAINS_CONFIGURATIONS, +} from '@core/network/constants' import { IPersistedProfile } from '@core/profile/interfaces' export function prodProfileMigration14To15(existingProfile: unknown): Promise { const profile = existingProfile as IPersistedProfile & { network: { + id: StardustNetworkId explorerUrl?: string } } @@ -16,6 +22,8 @@ export function prodProfileMigration14To15(existingProfile: unknown): Promise & { status: boolean transactionHash: string - transactionIndex: number - blockNumber: number + transactionIndex?: number + blockNumber?: number + timestamp: number to: string from: string - gasUsed: number + gasUsed?: number estimatedGas?: number gasLimit?: number nonce?: number diff --git a/packages/shared/src/lib/core/transactions/utils/buildLocalEvmTransactionFromBlockscoutTransaction.ts b/packages/shared/src/lib/core/transactions/utils/buildLocalEvmTransactionFromBlockscoutTransaction.ts index 8feb13f9f4..021f1b97be 100644 --- a/packages/shared/src/lib/core/transactions/utils/buildLocalEvmTransactionFromBlockscoutTransaction.ts +++ b/packages/shared/src/lib/core/transactions/utils/buildLocalEvmTransactionFromBlockscoutTransaction.ts @@ -10,9 +10,10 @@ export function buildPersistedEvmTransactionFromBlockscoutTransaction( transactionHash: blockscoutTransaction.hash, transactionIndex: blockscoutTransaction.position, blockNumber: blockscoutTransaction.block, + timestamp: new Date(blockscoutTransaction.timestamp).getTime(), confirmations: blockscoutTransaction.confirmations, - from: blockscoutTransaction.from.hash.toLowerCase(), - to: blockscoutTransaction.to.hash.toLowerCase(), + from: blockscoutTransaction.from?.hash?.toLowerCase(), + to: blockscoutTransaction.to?.hash?.toLowerCase(), gasUsed: Number(blockscoutTransaction.gas_used), type: TransactionType.Legacy, ...(blockscoutTransaction.nonce && { nonce: blockscoutTransaction.nonce }), @@ -21,6 +22,5 @@ export function buildPersistedEvmTransactionFromBlockscoutTransaction( ...(blockscoutTransaction.value && { value: blockscoutTransaction.value }), ...(blockscoutTransaction.raw_input && blockscoutTransaction.raw_input !== '0x' && { data: blockscoutTransaction.raw_input }), // Not sure if this is the right field - ...(blockscoutTransaction.timestamp && { timestamp: new Date(blockscoutTransaction.timestamp).getTime() }), } } diff --git a/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts b/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts index 8ee519aba6..ff0b7eb4d8 100644 --- a/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts +++ b/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts @@ -1,12 +1,12 @@ -import { IAccountState } from '@core/account' +import { getAddressFromAccountForNetwork, IAccountState } from '@core/account' import { ActivityDirection, EvmActivity, calculateAndAddPersistedNftBalanceChange, calculateAndAddPersistedTokenBalanceChange, } from '@core/activity' -import { addAccountActivity } from '@core/activity/stores' -import { generateEvmActivityFromLocalEvmTransaction } from '@core/activity/utils/evm' +import { addAccountActivity, updateActivityByActivityId } from '@core/activity/stores' +import { generateBaseEvmActivity, generateEvmActivityFromLocalEvmTransaction } from '@core/activity/utils/evm' import { EvmTransactionData } from '@core/layer-2' import { IEvmNetwork } from '@core/network' import { LocalEvmTransaction } from '@core/transactions' @@ -25,30 +25,58 @@ export async function sendAndPersistTransactionFromEvm( profileId: string, account: IAccountState ): Promise { - const transactionReceipt = await sendSignedEvmTransaction(evmNetwork, signedTransaction) - if (!transactionReceipt) { - throw Error('No transaction receipt!') - } + let transactionHash = '' + let evmTransaction: LocalEvmTransaction + let activityId: string | undefined - // We manually add a timestamp to mitigate balance change activities - // taking precedence over send/receive activities - const evmTransaction: LocalEvmTransaction = { - ...preparedTransaction, - status: true, - transactionHash: transactionReceipt.transactionHash.toString(), - transactionIndex: Number(transactionReceipt.transactionIndex), - blockNumber: Number(transactionReceipt.blockNumber), - to: transactionReceipt.to, - from: transactionReceipt.from, - gasUsed: Number(transactionReceipt.gasUsed), - estimatedGas: Number(preparedTransaction.estimatedGas), - nonce: Number(preparedTransaction.nonce), - gasLimit: Number(preparedTransaction.gasLimit), - timestamp: Date.now(), - confirmations: 0, - } - await persistEvmTransaction(profileId, account, evmNetwork, evmTransaction) - return evmTransaction.transactionHash + return new Promise((resolve, reject) => { + void sendSignedEvmTransaction(evmNetwork, signedTransaction) + .on('transactionHash', async (hash) => { + try { + transactionHash = hash + evmTransaction = { + ...preparedTransaction, + status: true, + transactionHash: transactionHash.toString(), + estimatedGas: Number(preparedTransaction.estimatedGas), + nonce: Number(preparedTransaction.nonce), + gasLimit: Number(preparedTransaction.gasLimit), + timestamp: Date.now(), + confirmations: 0, + to: preparedTransaction.to?.toString() ?? '', + from: getAddressFromAccountForNetwork(account, evmNetwork.id) ?? '', + } + activityId = await persistEvmTransaction(profileId, account, evmNetwork, evmTransaction) + resolve(transactionHash) + } catch (error) { + reject(error) + } + }) + .on('receipt', async (receipt) => { + if (!activityId) { + return + } + + evmTransaction = { + ...evmTransaction, + transactionIndex: Number(receipt.transactionIndex), + blockNumber: Number(receipt.blockNumber), + to: receipt.to, + from: receipt.from, + gasUsed: Number(receipt.gasUsed), + } + // TODO: Do we need to update the persisted transactions here? Because we now have more information that when we sent it... + + const activity = await generateBaseEvmActivity(evmTransaction, evmNetwork, account) + + updateActivityByActivityId(account.index, activityId, activity) + + void startEvmConfirmationPoll(evmTransaction, evmNetwork, account.index, profileId) + }) + .on('error', (error) => { + reject(error) + }) + }) } async function persistEvmTransaction( @@ -56,14 +84,13 @@ async function persistEvmTransaction( account: IAccountState, evmNetwork: IEvmNetwork, evmTransaction: LocalEvmTransaction -): Promise { +): Promise { const networkId = evmNetwork.id addLocalTransactionToPersistedTransaction(profileId, account.index, networkId, [evmTransaction]) const activity = await generateEvmActivityFromLocalEvmTransaction(evmTransaction, evmNetwork, account) if (!activity) { return } - startEvmConfirmationPoll(evmTransaction, evmNetwork, account.index, profileId) addAccountActivity(account.index, activity) @@ -84,6 +111,8 @@ async function persistEvmTransaction( addAccountActivity(recipientAccount.index, receiveActivity) createHiddenBalanceChange(profileId, recipientAccount, receiveActivity) } + + return activity.id } // Hidden balance changes mitigate duplicate activities for L2 transactions (balance changed & sent/receive activities). diff --git a/packages/shared/src/lib/core/wallet/actions/sendSignedEvmTransaction.ts b/packages/shared/src/lib/core/wallet/actions/sendSignedEvmTransaction.ts index 691d01783e..02f2c616d1 100644 --- a/packages/shared/src/lib/core/wallet/actions/sendSignedEvmTransaction.ts +++ b/packages/shared/src/lib/core/wallet/actions/sendSignedEvmTransaction.ts @@ -2,15 +2,17 @@ import { updateSelectedAccount } from '@core/account/stores' import { closePopup } from '../../../../../../desktop/lib/auxiliary/popup' import { IEvmNetwork } from '@core/network' import { getIsActiveLedgerProfile } from '@core/profile/stores' -import { TransactionReceipt } from 'web3' +import { DEFAULT_RETURN_FORMAT, TransactionReceipt, Web3PromiEvent } from 'web3' +import { SendSignedTransactionEvents } from 'web3/lib/commonjs/eth.exports' -export async function sendSignedEvmTransaction( +export function sendSignedEvmTransaction( evmNetwork: IEvmNetwork, signedTransaction: string -): Promise { +): Web3PromiEvent> { try { updateSelectedAccount({ isTransferring: true }) - return await evmNetwork.provider.eth.sendSignedTransaction(signedTransaction) + + return evmNetwork.provider.eth.sendSignedTransaction(signedTransaction) } catch (err) { if (getIsActiveLedgerProfile()) { closePopup({ forceClose: true }) diff --git a/packages/shared/src/lib/core/wallet/utils/send/startEvmConfirmationPoll.ts b/packages/shared/src/lib/core/wallet/utils/send/startEvmConfirmationPoll.ts index 71a4e41f39..898c666317 100644 --- a/packages/shared/src/lib/core/wallet/utils/send/startEvmConfirmationPoll.ts +++ b/packages/shared/src/lib/core/wallet/utils/send/startEvmConfirmationPoll.ts @@ -3,40 +3,87 @@ import { updateEvmActivity } from '@core/activity/stores' import { FAILED_CONFIRMATION, IEvmNetwork } from '@core/network' import { LocalEvmTransaction } from '@core/transactions' import { addLocalTransactionToPersistedTransaction } from '@core/transactions/stores' -import { MILLISECONDS_PER_SECOND } from '@core/utils' +import { MILLISECONDS_PER_SECOND } from '@core/utils/constants' +import { Converter } from '@core/utils/convert' +import { BigIntLike } from '@ethereumjs/util' -export function startEvmConfirmationPoll( +export async function startEvmConfirmationPoll( transaction: LocalEvmTransaction, evmNetwork: IEvmNetwork, accountIndex: number, profileId: string -): void { - const { transactionHash, blockNumber } = transaction - const pollInterval = evmNetwork.averageBlockTimeInSeconds * MILLISECONDS_PER_SECOND +): Promise { + const { transactionHash, blockNumber: transactionBlockNumber } = transaction + if (!transactionBlockNumber) { + return + } + + let isLogicInProgress = false - const poll = async () => { - const currentBlockNumber = await evmNetwork.provider.eth.getBlockNumber() - let confirmations = Number(BigInt(currentBlockNumber) - BigInt(blockNumber)) + async function _pollingLogic(currentBlockNumber: BigIntLike, onCancel: () => void): Promise { + if (isLogicInProgress) return + + isLogicInProgress = true + if (currentBlockNumber === null || currentBlockNumber === undefined) { + isLogicInProgress = false + return + } + let confirmations = Number( + Converter.bigIntLikeToBigInt(currentBlockNumber) - Converter.bigIntLikeToBigInt(transactionBlockNumber) + ) let inclusionState = InclusionState.Pending + if (confirmations >= evmNetwork.blocksUntilConfirmed) { try { - await evmNetwork.provider.eth.getTransactionReceipt(transactionHash) - inclusionState = InclusionState.Confirmed + const receipt = await evmNetwork.provider.eth.getTransactionReceipt(transactionHash) + if (receipt && receipt.blockNumber) { + inclusionState = InclusionState.Confirmed + } else { + throw new Error('Transaction receipt not found') + } } catch (error) { inclusionState = InclusionState.Conflicting confirmations = FAILED_CONFIRMATION + console.error(error) } finally { + // TODO: is this updating the existing local transaction? addLocalTransactionToPersistedTransaction(profileId, accountIndex, evmNetwork.id, [ { ...transaction, confirmations }, ]) updateEvmActivity(accountIndex, transactionHash, { inclusionState }) - clearInterval(interval) + + onCancel() } } else { + // TODO: should we update the local transaction here everytime we get a new confirmation? updateEvmActivity(accountIndex, transactionHash, { inclusionState }) } + isLogicInProgress = false } - const interval = setInterval(() => void poll(), pollInterval) + try { + const subscription = await evmNetwork.provider.eth.subscribe('newBlockHeaders') + subscription.on('data', (result) => { + void _pollingLogic(result?.number ?? 0, subscription.unsubscribe) + }) + + subscription.on('error', (error: Error) => { + console.error('Error in newBlockHeaders subscription:', error) + }) + } catch (error) { + const _error = (error ?? {}) as { name?: string; code?: number } + + if (_error?.name === 'SubscriptionError' && _error?.code === 603) { + async function _intervalLogic(): Promise { + const currentBlockNumber = await evmNetwork.provider.eth.getBlockNumber() + void _pollingLogic(currentBlockNumber, () => clearInterval(intervalId)) + } + + // TODO average block time might be undefined and what is the best case for a fallback? + const pollInterval = (evmNetwork.averageBlockTimeInSeconds ?? 10) * MILLISECONDS_PER_SECOND + + const intervalId = setInterval(() => void _intervalLogic(), pollInterval) + } + } }