Skip to content

Commit

Permalink
feat: fetch token balances for evm chains (#2380)
Browse files Browse the repository at this point in the history
* rename L2 to EVM

* implement getBalance

* check if profileId updated

* revert poll interval

---------

Co-authored-by: Tuditi <[email protected]>
  • Loading branch information
MarkNerdi and Tuditi authored Apr 30, 2024
1 parent 2fd36c0 commit c6f6b5b
Show file tree
Hide file tree
Showing 16 changed files with 117 additions and 100 deletions.
4 changes: 2 additions & 2 deletions packages/desktop/components/NetworkCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { openUrlInBrowser } from '@core/app'
import { handleError } from '@core/error/handlers'
import { localize } from '@core/i18n'
import { generateAndStoreEvmAddressForAccounts, pollL2BalanceForAccount } from '@core/layer-2/actions'
import { generateAndStoreEvmAddressForAccounts, pollEvmBalancesForAccount } from '@core/layer-2/actions'
import { LedgerAppName } from '@core/ledger'
import { ExplorerEndpoint, Network, NetworkNamespace, getDefaultExplorerUrl, setSelectedChain } from '@core/network'
import { ProfileType } from '@core/profile'
Expand Down Expand Up @@ -67,7 +67,7 @@
network.coinType,
$selectedAccount as IAccountState
)
pollL2BalanceForAccount($activeProfile.id, $selectedAccount as IAccountState)
pollEvmBalancesForAccount($activeProfile.id, $selectedAccount as IAccountState)
if ($activeProfile.type === ProfileType.Ledger) {
$networkConfigRouter.goTo(NetworkConfigRoute.ConfirmLedgerEvmAddress)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/desktop/components/menus/TokenListMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import { localize } from '@core/i18n'
import { loadTokensForAllAccountBalances } from '@core/token/actions'
import { PopupId, closePopup, openPopup } from '../../lib/auxiliary/popup'
import { fetchL2BalanceForAllAccounts } from '@core/layer-2'
import { fetchEvmBalancesForAllAccounts } from '@core/layer-2'
let menu: Menu | undefined = undefined
function onSyncTokensClick(): void {
fetchL2BalanceForAllAccounts($activeProfileId as string, true)
fetchEvmBalancesForAllAccounts($activeProfileId as string, true)
showNotification({
variant: 'success',
text: localize('notifications.syncTokens.success'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import { selectedAccount } from '@core/account/stores'
import { setClipboard } from '@core/utils'
import { getActiveNetworkId, getNetwork, NetworkId, NetworkNamespace } from '@core/network'
import { generateAndStoreEvmAddressForAccounts, pollL2BalanceForAccount } from '@core/layer-2/actions'
import { activeProfile, activeProfileId } from '@core/profile/stores'
import { generateAndStoreEvmAddressForAccounts, pollEvmBalancesForAccount } from '@core/layer-2/actions'
import { activeProfile } from '@core/profile/stores'
import { checkActiveProfileAuth } from '@core/profile/actions'
import { LedgerAppName } from '@core/ledger'
import PopupTemplate from '../PopupTemplate.svelte'
Expand Down Expand Up @@ -42,7 +42,7 @@
try {
await generateAndStoreEvmAddressForAccounts($activeProfile.type, coinType, account)
pollL2BalanceForAccount($activeProfileId as string, account)
pollEvmBalancesForAccount($activeProfile.id, account)
updateNetworkNameAndAddress()
} catch (error) {
handleError(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { AvatarGroup, Copyable, Text } from '@bloomwalletio/ui'
import { FormattedBalance } from '@components'
import { formatCurrency, localize } from '@core/i18n'
import { generateAndStoreEvmAddressForAccounts, pollL2BalanceForAccount } from '@core/layer-2/actions'
import { generateAndStoreEvmAddressForAccounts, pollEvmBalancesForAccount } from '@core/layer-2/actions'
import { LedgerAppName } from '@core/ledger'
import { Network, NetworkNamespace, setSelectedChain } from '@core/network'
import { MimeType, Nft } from '@core/nfts'
Expand Down Expand Up @@ -89,7 +89,7 @@
try {
await generateAndStoreEvmAddressForAccounts($activeProfile.type, network.coinType, account)
pollL2BalanceForAccount($activeProfile.id, account)
pollEvmBalancesForAccount($activeProfile.id, account)
if ($activeProfile.type === ProfileType.Ledger) {
setSelectedChain(network)
toggleDashboardDrawer({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { updateAccountForConnectedDapps } from '@auxiliary/wallet-connect/actions'
import { pollL2BalanceForAccount } from '@core/layer-2/actions/pollL2BalanceForAccount'
import { pollEvmBalancesForAccount } from '@core/layer-2/actions/pollEvmBalancesForAccount'
import { activeAccounts, getActiveProfileId, updateActiveProfile } from '@core/profile/stores'
import { clearFilters } from '@core/utils'
import { resetSendOptionIndex } from '@core/wallet/stores'
Expand All @@ -14,7 +14,7 @@ export function setSelectedAccount(index: number): void {
updateAccountForConnectedDapps(account)
updateActiveProfile({ lastUsedAccountIndex: index })
clearFilters()
pollL2BalanceForAccount(activeProfileId, account)
pollEvmBalancesForAccount(activeProfileId, account)
resetSendOptionIndex()
} else {
throw new Error(`Account with ID ${index} cannot be found!`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createNewAccount } from './createNewAccount'
import { setSelectedAccount } from './setSelectedAccount'
import { getActiveProfile } from '@core/profile/stores'
import { ProfileType } from '@core/profile'
import { generateAndStoreEvmAddressForAccounts, pollL2BalanceForAccount } from '@core/layer-2/actions'
import { generateAndStoreEvmAddressForAccounts, pollEvmBalancesForAccount } from '@core/layer-2/actions'
import { getEvmNetworks } from '@core/network/stores'
import { IError } from '@core/error/interfaces'

Expand All @@ -24,7 +24,7 @@ export async function tryCreateAdditionalAccount(alias: string, color: string):
const coinType = getEvmNetworks()[0]?.coinType
if (coinType !== undefined) {
void generateAndStoreEvmAddressForAccounts(activeProfile.type, coinType, account)
void pollL2BalanceForAccount(activeProfile.id, account)
void pollEvmBalancesForAccount(activeProfile.id, account)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IAccountState } from '@core/account/interfaces'
import { getEvmNetworks } from '@core/network/stores'
import { ITokenBalance } from '@core/token'
import { setLayer2AccountBalanceForChain } from '@core/layer-2/stores'
import { activeProfileId } from '@core/profile/stores'
import { get } from 'svelte/store'

export function fetchEvmBalancesForAccount(profileId: string, account: IAccountState): void {
const evmNetworks = getEvmNetworks()
evmNetworks.forEach(async (evmNetwork) => {
try {
const tokenBalance: ITokenBalance = (await evmNetwork.getBalance(account)) ?? {}

if (get(activeProfileId) === profileId) {
setLayer2AccountBalanceForChain(account.index, evmNetwork.id, tokenBalance)
}
} catch (error) {
console.error(error)
}
})
}

This file was deleted.

4 changes: 2 additions & 2 deletions packages/shared/src/lib/core/layer-2/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ export * from './buildUnwrapAssetParameters'
export * from './buildUnwrapAssetTargetAddress'
export * from './canAccountMakeEvmTransaction'
export * from './checkForUntrackedTokens'
export * from './fetchL2BalanceForAccount'
export * from './fetchEvmBalancesForAccount'
export * from './getGasFeeForLayer1ToLayer2Transaction'
export * from './generateAndStoreEvmAddressForAccounts'
export * from './getIscTransferSmartContractData'
export * from './getLayer2MetadataForTransfer'
export * from './getLayer2NetworkFromAddress'
export * from './getNetworkFromAddress'
export * from './pollL2BalanceForAccount'
export * from './pollEvmBalancesForAccount'
export * from './setGasFee'
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { IAccountState } from '@core/account/interfaces'
import { checkForUntrackedNfts } from '@core/nfts/actions'
import { LAYER2_TOKENS_POLL_INTERVAL } from '../constants'
import { checkForUntrackedTokens, fetchL2BalanceForAccount } from '.'
import { checkForUntrackedTokens, fetchEvmBalancesForAccount } from '.'
import { handleError } from '@core/error/handlers'

let pollInterval: number

export function pollL2BalanceForAccount(profileId: string, account: IAccountState): void {
export function pollEvmBalancesForAccount(profileId: string, account: IAccountState): void {
try {
clearL2TokensPoll()
checkForUntrackedTokens(account)
void checkForUntrackedNfts(account)
fetchL2BalanceForAccount(profileId, account)
fetchEvmBalancesForAccount(profileId, account)
pollInterval = window.setInterval(() => {
fetchL2BalanceForAccount(profileId, account)
fetchEvmBalancesForAccount(profileId, account)
}, LAYER2_TOKENS_POLL_INTERVAL)
} catch (err) {
handleError(err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { get } from 'svelte/store'
import { activeAccounts } from '@core/profile/stores'
import { checkForUntrackedTokens, fetchL2BalanceForAccount } from '../actions'
import { checkForUntrackedTokens, fetchEvmBalancesForAccount } from '../actions'
import { checkForUntrackedNfts } from '@core/nfts/actions'

export function fetchL2BalanceForAllAccounts(profileId: string, addPreviouslyUntracked?: boolean): void {
export function fetchEvmBalancesForAllAccounts(profileId: string, addPreviouslyUntracked?: boolean): void {
for (const account of get(activeAccounts)) {
try {
checkForUntrackedTokens(account, addPreviouslyUntracked)
void checkForUntrackedNfts(account)
fetchL2BalanceForAccount(profileId, account)
fetchEvmBalancesForAccount(profileId, account)
} catch (err) {
console.error(err)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/lib/core/layer-2/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export * from './buildEvmTransactionDataForToken'
export * from './calculateEstimatedGasFeeFromTransactionData'
export * from './calculateMaxGasFeeFromTransactionData'
export * from './fetchIscAssetsForAccount'
export * from './fetchL2BalanceForAllAccounts'
export * from './fetchEvmBalancesForAllAccounts'
export * from './getAmountFromEvmTransactionValue'
export * from './getEvmTokenMetadata'
export * from './getErc20TransferSmartContractData'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@ import { EvmNetworkType, NetworkHealth, NetworkNamespace, ChainId } from '../enu
import { IBlock, IEvmNetwork, IBaseEvmNetworkConfiguration } from '../interfaces'
import { CoinType } from '@iota/sdk/out/types'
import { EvmNetworkId, Web3Provider } from '../types'
import { IBaseToken } from '@core/token'
import { NETWORK_STATUS_POLL_INTERVAL } from '@core/network/constants'
import { BASE_TOKEN_ID, IBaseToken, ITokenBalance, TokenTrackingStatus } from '@core/token'
import features from '@features/features'
import { updateErc721NftsOwnership } from '@core/nfts/actions'
import { getOrRequestTokenFromPersistedTokens } from '@core/token/actions'
import { IAccountState } from '@core/account'
import { Converter } from '@core/utils'
import { getActiveProfile } from '@core/profile/stores'
import { IError } from '@core/error/interfaces'

export class BaseEvmNetwork implements IEvmNetwork {
public readonly provider: Web3Provider
Expand Down Expand Up @@ -94,4 +101,49 @@ export class BaseEvmNetwork implements IEvmNetwork {
const number = await this.provider.eth.getBlockNumber()
return this.provider.eth.getBlock(number)
}

async getBalance(account: IAccountState): Promise<ITokenBalance | undefined> {
const evmAddress = account.evmAddresses?.[this.coinType]
if (!evmAddress) {
return
}

if (features.collectibles.erc721.enabled) {
void updateErc721NftsOwnership(account, this.id)
}

const rawBalance = await this.provider.eth.getBalance(evmAddress)
const tokenBalance = { [BASE_TOKEN_ID]: Converter.bigIntLikeToBigInt(rawBalance) }

const erc20Balances = await this.getErc20BalancesForAddress(evmAddress)
for (const [tokenId, balance] of Object.entries(erc20Balances)) {
await getOrRequestTokenFromPersistedTokens(tokenId, this.id)
tokenBalance[tokenId] = Number.isNaN(Number(balance)) ? BigInt(0) : balance
}

return tokenBalance
}

async getErc20BalancesForAddress(evmAddress: string): Promise<ITokenBalance> {
const trackedTokens = getActiveProfile()?.trackedTokens?.[this.id] ?? {}
const erc20TokenBalances: ITokenBalance = {}
for (const [erc20Address, trackingStatus] of Object.entries(trackedTokens)) {
try {
if (trackingStatus === TokenTrackingStatus.Untracked) {
continue
}

const contract = this.getContract(ContractType.Erc20, erc20Address)
if (!contract || !this.coinType) {
continue
}
const rawBalance = await contract.methods.balanceOf(evmAddress).call()
erc20TokenBalances[erc20Address] = Converter.bigIntLikeToBigInt(rawBalance)
} catch (err) {
const error = (err as IError)?.message ?? err
console.error(error)
}
}
return erc20TokenBalances
}
}
19 changes: 15 additions & 4 deletions packages/shared/src/lib/core/network/classes/isc-chain.class.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { IBlock, IIscChainConfiguration, IIscChainMetadata } from '../interfaces'
import { IIscChainConfiguration, IIscChainMetadata } from '../interfaces'
import { Converter } from '@core/utils'
import { BaseEvmNetwork } from './base-evm-network.class'
import { IAccountState } from '@core/account/interfaces'
import { ITokenBalance } from '@core/token/interfaces'
import { fetchIscAssetsForAccount } from '@core/layer-2/utils'
import { getActiveProfileId } from '@core/profile/stores'

export class IscChain extends BaseEvmNetwork {
private readonly _chainApi: string
Expand Down Expand Up @@ -47,9 +51,16 @@ export class IscChain extends BaseEvmNetwork {
return (await response.json()) as IIscChainMetadata
}

async getLatestBlock(): Promise<IBlock> {
const number = await this.provider.eth.getBlockNumber()
return this.provider.eth.getBlock(number)
async getBalance(account: IAccountState): Promise<ITokenBalance | undefined> {
const evmAddress = account.evmAddresses?.[this.coinType]
if (!evmAddress) {
return undefined
}

const tokenBalance = (await super.getBalance(account)) ?? {}
const iscBalance = (await fetchIscAssetsForAccount(getActiveProfileId(), evmAddress, this, account)) ?? {}

return { ...tokenBalance, ...iscBalance }
}

async getGasEstimate(hex: string): Promise<bigint> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { EvmNetworkId, Web3Provider } from '../types'
import { IBlock } from './block.interface'
import { IBaseNetwork, IBaseNetworkMetadata } from './base-network.interface'
import { IIscChainMetadata } from './isc-chain-metadata.interface'
import { ITokenBalance } from '@core/token/interfaces'
import { IAccountState } from '@core/account/interfaces'

export interface IIscChain extends IEvmNetwork {
apiEndpoint: string
Expand All @@ -26,6 +28,7 @@ export interface IEvmNetwork extends IBaseNetwork, IBaseNetworkMetadata {
provider: Web3Provider

getGasPrice(): Promise<bigint | undefined>
getBalance(account: IAccountState): Promise<ITokenBalance | undefined>

getContract(type: ContractType, address: string): Contract
getLatestBlock(): Promise<IBlock>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { generateAndStoreActivitiesForAllAccounts } from '@core/activity/actions
import { Platform } from '@core/app/classes'
import { AppContext } from '@core/app/enums'
import { handleError } from '@core/error/handlers'
import { fetchL2BalanceForAllAccounts } from '@core/layer-2/utils'
import { fetchEvmBalancesForAllAccounts } from '@core/layer-2/utils'
import { pollLedgerDeviceState } from '@core/ledger/actions'
import { pollMarketPrices } from '@core/market/actions'
import { loadNftsForActiveProfile } from '@core/nfts/actions'
Expand Down Expand Up @@ -103,7 +103,7 @@ export async function login(loginOptions?: ILoginOptions): Promise<void> {
incrementLoginProgress()
subscribeToWalletApiEventsForActiveProfile()
await startBackgroundSync({ syncIncomingTransactions: true })
fetchL2BalanceForAllAccounts(id)
fetchEvmBalancesForAllAccounts(_activeProfile.id)
void fetchAndPersistTransactionsForAccounts(_activeProfile.id, get(activeAccounts))

// Step 8: finish login
Expand Down

0 comments on commit c6f6b5b

Please sign in to comment.