Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ethereum confirmed blocks property #2533

Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@
import { EvmActivityType } from '@core/activity/enums/evm'
import { NftStandard } from '@core/nfts/enums'
import { convertCamelCaseToPhrase } from '@core/utils/string'
import { ConfirmationPill } from '@ui'

export let activity: Activity

let typePill = ''
let standardPill = ''

$: activity, setPills()
function setPills() {
function setPills(): void {
if (activity.namespace === NetworkNamespace.Stardust) {
if (activity.type === StardustActivityType.Basic) {
if (activity.tokenTransfer && activity.tokenTransfer?.tokenId !== BASE_TOKEN_ID) {
const token = getTokenFromActivity(activity)
typePill = 'token'
standardPill = token.standard
standardPill = token?.standard ?? ''
} else {
typePill = 'baseCoin'
standardPill = ''
Expand Down Expand Up @@ -80,5 +81,8 @@
{standardPill}
</Pill>
{/if}
{#if activity.namespace === NetworkNamespace.Evm}
<ConfirmationPill {activity} />
{/if}
</div>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
<GovernanceAvatar governanceAction={activity.governanceAction} size="lg" />
{:else if token}
<TokenAvatar {token} hideNetworkBadge size="lg" />
{:else if showNft}
{:else if showNft && nft}
<NftAvatar {nft} size="lg" shape="square" />
{:else if activity.type === EvmActivityType.ContractCall}
<Avatar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,7 @@
<Tabs bind:selectedTab {tabs} />
{/if}
{#if selectedTab.key === PopupTab.Transaction}
<EvmGenericInformation
time={activity.time}
sourceNetworkId={activity.sourceNetworkId}
destinationNetworkId={activity.destinationNetworkId}
maxGasFee={activity.maxGasFee}
transactionFee={activity.transactionFee}
/>
<EvmGenericInformation {activity} />
{:else if selectedTab.key === PopupTab.NftMetadata && nft}
<NftMetadataTable {nft} />
{:else if selectedTab.key === PopupTab.SmartContract && (activity.type === EvmActivityType.ContractCall || activity.type === EvmActivityType.TokenTransfer || activity.type === EvmActivityType.TokenMinting)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
<script lang="ts">
import { Table } from '@bloomwalletio/ui'
import { EvmActivity, InclusionState } from '@core/activity'
import { getFormattedTimeStamp, localize } from '@core/i18n'
import { NetworkId, getNetwork } from '@core/network'
import { getNetwork } from '@core/network'
import { formatTokenAmount } from '@core/token'
import { NetworkLabel } from '@ui'

export let time: Date
export let sourceNetworkId: NetworkId
export let destinationNetworkId: NetworkId
export let maxGasFee: bigint | undefined = undefined
export let transactionFee: bigint | undefined = undefined
export let activity: EvmActivity

$: formattedTransactionTime = getFormattedTimeStamp(time)
$: formattedTransactionTime = getFormattedTimeStamp(activity.time)

$: formattedMaxGasFee = formatAmount(maxGasFee)
$: formattedTransactionFee = formatAmount(transactionFee)
$: formattedMaxGasFee = formatAmount(activity.maxGasFee)
$: formattedTransactionFee = formatAmount(activity.transactionFee)

function formatAmount(amount: bigint | undefined): string | undefined {
return amount ? formatTokenAmount(amount, getNetwork(sourceNetworkId)?.baseToken) : undefined
return amount ? formatTokenAmount(amount, getNetwork(activity.sourceNetworkId)?.baseToken) : undefined
}
</script>

Expand All @@ -28,7 +25,7 @@
slot: {
component: NetworkLabel,
props: {
networkId: destinationNetworkId,
networkId: activity.destinationNetworkId,
},
},
},
Expand All @@ -44,5 +41,12 @@
key: localize('general.transactionFee'),
value: formattedTransactionFee,
},
{
key: localize('general.status'),
value:
activity.inclusionState === InclusionState.Confirmed
? localize('general.confirmed')
: localize('general.pending'),
},
]}
/>
31 changes: 31 additions & 0 deletions packages/shared/src/components/pills/ConfirmationPill.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import { Pill } from '@bloomwalletio/ui'
import { Activity, InclusionState } from '@core/activity'
import { localize } from '@core/i18n'
import { getEvmNetwork } from '@core/network/stores'
import { getPersistedTransaction } from '@core/transactions/stores'

export let activity: Activity

let tooltip = ''

async function onHover(): Promise<void> {
tooltip = 'loading...'
const transaction = getPersistedTransaction(activity.transactionId)
const network = getEvmNetwork(activity?.sourceNetworkId)
const blockHeight = await network?.provider.eth.getBlockNumber()
const confirmations = BigInt(blockHeight ?? 0) - BigInt(transaction?.local?.blockNumber ?? 0)
tooltip = `
${localize('general.confirmed')}: ${confirmations}\n
${localize('general.required')}: ${network?.blocksUntilConfirmed}
`
}
</script>

{#if activity.inclusionState === InclusionState.Pending}
<div role="contentinfo" on:focus={onHover} on:mouseover={onHover}>
<Pill color="warning" compact tooltipEvent="hover" {tooltip}>
{localize('general.pending')}
</Pill>
</div>
{/if}
1 change: 1 addition & 0 deletions packages/shared/src/components/pills/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as AsyncStatusPill } from './AsyncStatusPill.svelte'
export { default as NetworkStatusPill } from './NetworkStatusPill.svelte'
export { default as NetworkTypePill } from './NetworkTypePill.svelte'
export { default as NftStandardPill } from './NftStandardPill.svelte'
export { default as ConfirmationPill } from './ConfirmationPill.svelte'

This file was deleted.

1 change: 0 additions & 1 deletion packages/shared/src/lib/core/activity/enums/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from './activity-action.enum'
export * from './activity-direction.enum'
export * from './activity-status.enum'
export * from './activity-type-filter-option.enum'
export * from './inclusion-state.enum'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get, writable } from 'svelte/store'
import { AsyncData, BaseStardustActivity, Activity } from '../types'
import { AsyncData, BaseStardustActivity, Activity, EvmActivity, BaseEvmActivity } from '../types'
import { NetworkNamespace } from '@core/network'

export const allAccountActivities = writable<{ [accountIndex: number]: Activity[] }>({})
Expand Down Expand Up @@ -35,7 +35,13 @@ export function setAccountActivities(accountIndex: number, accountActivities: Ac
})
}

export function getActivityByTransactionId(accountIndex: number, transactionId: string): Activity | undefined {
export function getActivityByTransactionId(
accountIndex: number,
transactionId: string | undefined
): Activity | undefined {
if (!transactionId) {
return
}
return get(allAccountActivities)?.[accountIndex]?.find((_activity) => _activity?.transactionId === transactionId)
}

Expand Down Expand Up @@ -97,6 +103,25 @@ export function updateAsyncDataByTransactionId(
})
}

export function updateEvmActivity(
accountIndex: number,
transactionHash: string,
partialActivity: Partial<BaseEvmActivity>
): void {
allAccountActivities.update((state) => {
const activity = state[accountIndex]?.find(
(_activity) => _activity.namespace === NetworkNamespace.Evm && _activity?.transactionId === transactionHash
) as EvmActivity

if (!activity) {
return state
}

Object.assign(activity, partialActivity)
return state
})
}

export function clearAccountActivities(): void {
allAccountActivities.set({})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,10 @@ import { MILLISECONDS_PER_SECOND } from '@core/utils/constants'
import { getSubjectFromAddress, isSubjectInternal } from '@core/wallet'
import { ActivityAction, ActivityDirection, InclusionState } from '../../enums'
import { BaseEvmActivity } from '../../types'
import type { BigIntLike } from '@ethereumjs/util'
import { LocalEvmTransaction } from '@core/transactions'

export async function generateBaseEvmActivity(
transaction: {
transactionHash: string
from: string
recipient: string
gasUsed: number
blockNumber: number
estimatedGas?: bigint
gasPrice?: BigIntLike
timestamp?: number
},
transaction: LocalEvmTransaction,
evmNetwork: IEvmNetwork,
account: IAccountState
): Promise<BaseEvmActivity> {
Expand All @@ -28,7 +19,7 @@ export async function generateBaseEvmActivity(
: ActivityDirection.Outgoing

const sender = getSubjectFromAddress(transaction.from, networkId)
const recipient = getSubjectFromAddress(transaction.recipient, networkId)
const recipient = getSubjectFromAddress(transaction.recipient ?? transaction.to, networkId)

const subject = direction === ActivityDirection.Outgoing ? recipient : sender
const isInternal = isSubjectInternal(recipient)
Expand All @@ -38,6 +29,10 @@ export async function generateBaseEvmActivity(
// https://discord.com/channels/397872799483428865/930642258427019354/1168854453005332490
const gasUsed = transaction.gasUsed || transaction.estimatedGas
const transactionFee = transaction.gasPrice ? calculateGasFee(gasUsed, transaction.gasPrice) : undefined
const inclusionState =
Number(transaction.confirmations) >= evmNetwork.blocksUntilConfirmed
? InclusionState.Confirmed
: InclusionState.Pending

return {
namespace: NetworkNamespace.Evm,
Expand All @@ -50,7 +45,7 @@ export async function generateBaseEvmActivity(
// transaction information
transactionId: transaction.transactionHash,
time: new Date(timestamp),
inclusionState: InclusionState.Confirmed,
inclusionState,

// sender / recipient information
sourceNetworkId: networkId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { IAccountState } from '@core/account'
import { getEvmNetworks } from '@core/network'
import { FAILED_CONFIRMATION, getEvmNetworks, IEvmNetwork } from '@core/network'
import { getPersistedTransactionsForChain } from '@core/transactions/stores'
import { EvmActivity } from '../../types'
import { generateEvmActivityFromPersistedTransaction } from './generateEvmActivityFromPersistedTransaction'
import { LocalEvmTransaction } from '@core/transactions'
import { startEvmConfirmationPoll } from '@core/wallet'

export async function generateEvmActivitiesFromEvmChains(
profileId: string,
Expand All @@ -13,6 +15,11 @@ export async function generateEvmActivitiesFromEvmChains(
for (const evmNetwork of getEvmNetworks()) {
const persistedTransactions = getPersistedTransactionsForChain(profileId, account.index, evmNetwork)
for (const persistedTransaction of persistedTransactions) {
const { local } = persistedTransaction
if (local) {
updateConfirmationsForEvmTransactions(evmNetwork, local, account.index, profileId)
}

try {
const activity = await generateEvmActivityFromPersistedTransaction(
persistedTransaction,
Expand All @@ -30,3 +37,22 @@ export async function generateEvmActivitiesFromEvmChains(

return activities
}

function updateConfirmationsForEvmTransactions(
evmNetwork: IEvmNetwork,
transaction: LocalEvmTransaction,
accountIndex: number,
profileId: string
): void {
if (transaction.confirmations === FAILED_CONFIRMATION) {
return
}

if (!transaction?.confirmations || transaction.confirmations < evmNetwork.blocksUntilConfirmed) {
try {
startEvmConfirmationPoll(transaction, evmNetwork, accountIndex, profileId)
} catch (error) {
console.error(error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,20 @@ async function generateBaseEvmActivityFromBlockscoutTransaction(
evmNetwork: IEvmNetwork,
account: IAccountState
): Promise<BaseEvmActivity> {
const baseActivity = await generateBaseEvmActivity(
{
recipient: blockscoutTransaction.to.hash.toLowerCase(),
from: blockscoutTransaction.from.hash.toLowerCase(),
gasUsed: Number(blockscoutTransaction.gas_used),
estimatedGas: localTransaction?.estimatedGas ? BigInt(localTransaction.estimatedGas) : undefined,
gasPrice: blockscoutTransaction.gas_price,
transactionHash: blockscoutTransaction.hash,
timestamp: new Date(blockscoutTransaction.timestamp).getTime(),
blockNumber: blockscoutTransaction.block,
},
evmNetwork,
account
)
const newLocalTransaction: LocalEvmTransaction = {
recipient: blockscoutTransaction.to.hash.toLowerCase(),
from: blockscoutTransaction.from.hash.toLowerCase(),
gasUsed: Number(blockscoutTransaction.gas_used),
gasPrice: blockscoutTransaction.gas_price,
transactionHash: blockscoutTransaction.hash,
timestamp: new Date(blockscoutTransaction.timestamp).getTime(),
blockNumber: blockscoutTransaction.block,
confirmations: blockscoutTransaction.confirmations,
status: localTransaction?.status ?? false,
transactionIndex: localTransaction?.transactionIndex ?? 0,
to: localTransaction?.to ?? blockscoutTransaction.to.hash.toLowerCase(),
}
const baseActivity = await generateBaseEvmActivity(newLocalTransaction, evmNetwork, account)

if (blockscoutTransaction.to.is_contract) {
baseActivity.recipient = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,8 @@ export async function generateEvmActivityFromLocalEvmTransaction(
account: IAccountState
): Promise<EvmActivity | undefined> {
if (!transaction.data) {
const { to, from, gasUsed, estimatedGas, gasPrice, transactionHash, timestamp, blockNumber } = transaction
// i.e must be a coin transfer
const baseActivity = await generateBaseEvmActivity(
{
recipient: to?.toString().toLowerCase(),
from: from?.toString().toLowerCase(),
gasUsed: Number(gasUsed),
estimatedGas: estimatedGas ? BigInt(estimatedGas) : undefined,
gasPrice: gasPrice ?? undefined,
transactionHash,
timestamp,
blockNumber,
},
evmNetwork,
account
)
const baseActivity = await generateBaseEvmActivity(transaction, evmNetwork, account)

return {
...baseActivity,
Expand All @@ -57,21 +43,8 @@ export async function generateEvmActivityFromLocalEvmTransaction(
return
}

const { to, from, gasUsed, estimatedGas, gasPrice, transactionHash, timestamp, blockNumber } = transaction
let baseActivity = await generateBaseEvmActivity(
{
recipient: parsedData.recipientAddress ?? to?.toString().toLowerCase(),
from: from?.toString().toLowerCase(),
gasUsed: Number(gasUsed),
estimatedGas: estimatedGas ? BigInt(estimatedGas) : undefined,
gasPrice: gasPrice ?? undefined,
transactionHash,
timestamp,
blockNumber,
},
evmNetwork,
account
)
transaction.recipient = parsedData.recipientAddress ?? transaction.to?.toString().toLowerCase()
let baseActivity = await generateBaseEvmActivity(transaction, evmNetwork, account)

baseActivity = {
...baseActivity,
Expand All @@ -81,7 +54,7 @@ export async function generateEvmActivityFromLocalEvmTransaction(
rawData: String(transaction.data ?? ''),
contract: {
type: SubjectType.SmartContract,
address: to?.toString().toLowerCase(),
address: transaction.to?.toString().toLowerCase(),
name: '',
verified: false,
},
Expand Down
Loading
Loading