kodadot/nft-gallery

View on GitHub
composables/useMultipleBalance.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'
import { storeToRefs } from 'pinia'
import {
  CHAINS,
  type ChainProperties,
  type Prefix,
} from '@kodadot1/static'
import { useIntervalFn } from '@vueuse/core'
import format from '@/utils/format/balance'
import { useFiatStore } from '@/stores/fiat'
import { calculateExactUsdFromToken } from '@/utils/calculation'
import { toDefaultAddress } from '@/utils/account'
import { getNativeBalance } from '@/utils/balance'
import { type ChainType, useIdentityStore } from '@/stores/identity'

export const networkToPrefix: Partial<Record<ChainType, Prefix>> = {
  polkadot: 'dot',
  kusama: 'ksm',
  kusamaHub: 'ahk',
  polkadotHub: 'ahp',
  base: 'base',
  immutablex: 'imx',
  mantle: 'mnt',
  // rococoHub: 'ahr',
}

export const prefixToNetwork: Partial<Record<Prefix, ChainType>> = {
  dot: 'polkadot',
  ksm: 'kusama',
  ahk: 'kusamaHub',
  ahp: 'polkadotHub',
  base: 'base',
  imx: 'immutablex',
  mnt: 'mantle',
  // ahr: 'rococoHub',
}

const getNetwork = (prefix: Prefix) => {
  return prefixToNetwork[prefix]
}

function calculateUsd(amount: string, tokenValue) {
  if (!amount) {
    return 0
  }

  const amountToNumber = Number(amount.replace(/\,/g, ''))

  return calculateExactUsdFromToken(amountToNumber, Number(tokenValue))
}

export default function (refetchPeriodically: boolean = false) {
  const { accountId } = useAuth()
  const { isTestnet, urlPrefix } = usePrefix()
  const identityStore = useIdentityStore()
  const fiatStore = useFiatStore()
  const { existentialDeposit } = useChain()
  const { getEvmBalance: fetchEvmBalance } = useBalance()
  const { apiInstanceByPrefix } = useApi()

  const {
    multiBalances,
    multiBalanceNetwork,
    getWalletVmAssets: assets,
  } = storeToRefs(identityStore)

  const currentNetwork = computed(() =>
    isTestnet ? 'test-network' : 'main-network',
  )

  const transferableCurrentChainBalance = computed<number | undefined>(() =>
    currentChainBalance.value
      ? Number(currentChainBalance.value) - existentialDeposit.value
      : undefined,
  )

  const chainBalances = computed(() => ({
    [Chain.KUSAMA]: multiBalances.value.chains.kusama?.ksm?.nativeBalance,
    [Chain.ASSETHUBKUSAMA]:
      multiBalances.value.chains.kusamaHub?.ksm?.nativeBalance,
    [Chain.POLKADOT]: multiBalances.value.chains.polkadot?.dot?.nativeBalance,
    [Chain.ASSETHUBPOLKADOT]:
      multiBalances.value.chains.polkadotHub?.dot?.nativeBalance,
    // decouple Chain from teleport
    [Chain.BASE]:
      multiBalances.value.chains.base?.eth?.nativeBalance,
    [Chain.IMMUTABLEX]:
      multiBalances.value.chains.immutablex?.eth?.nativeBalance,
    [Chain.MANTLE]:
      multiBalances.value.chains.mantle?.mnt?.nativeBalance,
  }))

  const currentChain = computed(() => prefixToChainMap[urlPrefix.value])
  const currentChainBalance = computed<string | undefined>(
    () => currentChain.value && chainBalances.value[currentChain.value],
  )
  const hasCurrentChainBalance = computed(
    () => currentChainBalance.value !== undefined,
  )

  async function getSubstrateBalance({
    chain,
    address,
    prefix,
    tokenId,
  }: {
    chain: ChainProperties
    address: string
    prefix: Prefix
    tokenId: number
  }) {
    const publicKey = decodeAddress(address)
    const prefixAddress = encodeAddress(publicKey, chain.ss58Format)

    const api = await apiInstanceByPrefix(prefix)

    const balance = await getNativeBalance({
      address: prefixAddress,
      api: api,
      tokenId,
    })

    return { balance: balance.toString(), prefixAddress }
  }

  async function getEvmBalance({ address, prefix }) {
    const balance = await fetchEvmBalance(address, prefix)

    return {
      balance: balance ?? '',
      prefixAddress: address as string,
    }
  }

  async function getBalance(chainName: string, token = 'KSM', tokenId = 0) {
    const prefix = networkToPrefix[chainName] as Prefix
    const chain = CHAINS[prefix]
    const currentAddress = accountId.value

    const defaultAddress = execByVm({
      SUB: () => toDefaultAddress(currentAddress),
      EVM: () => currentAddress,
    }, { prefix })

    const { balance: nativeBalance, prefixAddress } = (await execByVm({
      SUB: () =>
        getSubstrateBalance({
          address: currentAddress,
          prefix,
          chain,
          tokenId,
        }),
      EVM: () => getEvmBalance({ address: currentAddress, prefix }),
    }, { prefix })) as { balance: string, prefixAddress: string }

    const currentBalance = format(nativeBalance, chain.tokenDecimals, false)
    const selectedTokenId = String(tokenId)

    const usd = calculateUsd(
      currentBalance,
      fiatStore.getCurrentTokenValue(token as Token),
    )

    if (!identityStore.getWalletVmChains.includes(chainName)) {
      return
    }

    identityStore.setMultiBalances({
      address: defaultAddress,
      chains: {
        [chainName]: {
          [token.toLowerCase()]: {
            address: prefixAddress,
            balance: currentBalance,
            nativeBalance,
            usd,
            selected: selectedTokenId === String(tokenId),
          },
        },
      },
      chainName,
    })

    identityStore.setBalance(prefix, nativeBalance)
    identityStore.multiBalanceNetwork = currentNetwork.value

    return Promise.resolve()
  }

  const fetchFiatPrice = async (force: boolean) => {
    if (!force && fiatStore.incompleteFiatValues) {
      await fiatStore.fetchFiatPrice()
    }
  }

  const fetchMultipleBalance = async (
    onlyPrefixes: Prefix[] = [],
    forceFiat: boolean = false,
  ) => {
    await fetchFiatPrice(forceFiat)

    if (!accountId.value) {
      return
    }

    const chainNetworks = onlyPrefixes.map(getNetwork).filter(Boolean)

    const assetsToFetch = onlyPrefixes.length
      ? assets.value.filter(item => chainNetworks.includes(item.chain))
      : assets.value

    const promisses = assetsToFetch.map(item =>
      getBalance(item.chain, item.token, Number(item.tokenId)),
    )

    return Promise.allSettled(promisses)
  }

  onMounted(async () => {
    if (
      currentNetwork.value !== multiBalanceNetwork.value
      && refetchPeriodically
    ) {
      identityStore.resetMultipleBalances()
    }
  })

  const { pause: clearInterval } = useIntervalFn(
    () => {
      fetchMultipleBalance()
    },
    30000,
    { immediate: refetchPeriodically, immediateCallback: refetchPeriodically },
  )

  onUnmounted(() => {
    clearInterval()
  })

  return {
    multiBalances,
    chainBalances,
    currentNetwork,
    currentChainBalance,
    hasCurrentChainBalance,
    fetchMultipleBalance,
    transferableCurrentChainBalance,
  }
}