synapsecns/sanguine

View on GitHub
packages/synapse-interface/utils/sortTokens.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { multicall } from '@wagmi/core'
import { type Address, zeroAddress } from 'viem'

import multicallABI from '../constants/abis/multicall.json'
import erc20ABI from '../constants/abis/erc20.json'
import { Token } from '@/utils/types'
import { formatBigIntToString } from './bigint/format'
import { TokenAndBalance } from './actions/fetchPortfolioBalances'
import { CHAINS_BY_ID } from '@/constants/chains'
import { wagmiConfig } from '@/wagmiConfig'

export const sortByVisibilityRank = (tokens: Token[]) => {
  if (tokens === undefined) {
    return []
  }

  return Object.values(tokens).sort(
    (a, b) => b.visibilityRank - a.visibilityRank
  )
}

const sortArrayByBalance = (array) => {
  return array.sort((a, b) => {
    const balanceA = BigInt(a.balance || '')
    const balanceB = BigInt(b.balance || '')

    if (balanceA < balanceB) {
      return 1
    } else if (balanceA > balanceB) {
      return -1
    } else {
      return 0
    }
  })
}

export const sortByTokenBalance = async (
  tokens: Token[],
  chainId: number,
  address: any
): Promise<TokenAndBalance[]> => {
  const tokensWithBalances: any[] = []
  const multicallInputs = []

  if (chainId === undefined || !address) {
    tokens.forEach((token) => {
      tokensWithBalances.push({
        token,
        balance: 0n,
      })
    })
  } else {
    tokens.forEach((token) => {
      // deterministic multicall3 address on all eth chains
      const multicallAddress: Address = `0xcA11bde05977b3631167028862bE2a173976CA11`
      const tokenAddress = token?.addresses[chainId as keyof Token['addresses']]

      if (tokenAddress === zeroAddress || tokenAddress === undefined) {
        multicallInputs.push({
          address: multicallAddress,
          abi: multicallABI,
          functionName: 'getEthBalance',
          chainId,
          args: [address],
        })
      } else {
        multicallInputs.push({
          address: tokenAddress,
          abi: erc20ABI,
          functionName: 'balanceOf',
          chainId,
          args: [address],
        })
      }
    })
  }

  let multicallData: any[] | any
  if (multicallInputs.length > 0) {
    // @ts-ignore
    multicallData = await multicall(wagmiConfig, {
      contracts: multicallInputs,
      chainId,
    })
    return sortArrayByBalance(
      sortByVisibilityRank(
        multicallData.map(
          (
            tokenBalance: { result: bigint; status: string } | undefined,
            index: number
          ) => ({
            queriedChain: CHAINS_BY_ID[chainId],
            token: tokens[index],
            tokenAddress: tokens[index].addresses[chainId],
            balance: tokenBalance.result,
            parsedBalance: formatBigIntToString(
              tokenBalance.result,
              tokens[index]?.decimals[chainId],
              4
            ),
          })
        )
      )
    )
  }

  return tokensWithBalances
}

// Function to sort the tokens by priorityRank and alphabetically
export const sortTokensByPriorityRankAndAlpha = (arr: Token[]): Token[] => {
  // Create a copy of the array to prevent modifying the original one
  const sortedArr = arr && [...arr]

  return sortedArr?.sort((a, b) => {
    // Sort by priorityRank first
    if (a.priorityRank !== b.priorityRank) {
      return a.priorityRank - b.priorityRank
    }

    // If priorityRank is the same, sort by symbol
    return a.symbol.localeCompare(b.symbol)
  })
}

export const separateAndSortTokensWithBalances = (
  tokensAndBalances: TokenAndBalance[]
): Token[] => {
  const hasTokensAndBalances = tokensAndBalances.length > 0
  if (hasTokensAndBalances) {
    const tokensWithBalances = tokensAndBalances
      .filter((t) => !(t.balance === 0n))
      .map((t) => t.token)

    const a = sortTokensByPriorityRankAndAlpha(tokensWithBalances)

    const tokensWithNoBalances = tokensAndBalances
      .filter((t) => t.balance === 0n)
      .map((t) => t.token)

    const b = sortTokensByPriorityRankAndAlpha(tokensWithNoBalances)
    return [...a, ...b]
  } else {
    return []
  }
}