UNPKG

brahma-trade-widget

Version:

A React component for trade automation within the Brahma ecosystem.

208 lines (182 loc) 5.6 kB
import { readContracts } from "@wagmi/core" import { Address, erc20Abi, zeroAddress } from "viem" import multicallAbi from "../multicallAbi.json" import { MULTICALL_CONTRACT_ADDRESS, SCAM_TOKEN_ADDRESSES, SCAM_TOKEN_WORDS, SUPPORTED_CHAINS_IDS, } from "../constants" import { SupportedChainIds, TAsset } from "../types" import { formatUnits, sortAssets } from "./actions" import { baseWagmiConfig as wagmiConfig } from "../wagmi" type FetchContractBalance = { accountAddress: Address assets: TAsset[] } type TokenCall = { abi: unknown functionName: string address?: Address args?: readonly unknown[] } type CallType = { result: bigint status: "success" | "failure" } type TokenMultiCall = TokenCall[] const MULTI_CALL_FUNCTIONS = ["balanceOf"] const fetchAssetsBalanceMultiCall = async ({ accountAddress, assets, }: FetchContractBalance): Promise<TAsset[]> => { try { const filteredByDeployedChains = assets.filter((asset) => { return SUPPORTED_CHAINS_IDS.includes(asset.chainId) }) const getBalanceOfCall = ( tokenAddress: Address, chainId: SupportedChainIds, ): TokenMultiCall => { const erc20Config = { abi: erc20Abi, address: tokenAddress, chainId, } as const return [ { ...erc20Config, functionName: MULTI_CALL_FUNCTIONS[0], args: [accountAddress], }, ] } const getETHBalanceOfCall = ( chainId: SupportedChainIds, ): TokenMultiCall => { const callConfig = { abi: multicallAbi, address: MULTICALL_CONTRACT_ADDRESS, chainId, } as const return [ { ...callConfig, functionName: "getEthBalance", args: [accountAddress], }, ] } const tokenCalls: TokenMultiCall = [] filteredByDeployedChains .filter((asset) => asset.address.includes("0x")) .forEach((token) => { const tokenCall = token.address.toLowerCase() === zeroAddress.toLowerCase() ? getETHBalanceOfCall(token.chainId) : getBalanceOfCall(token.address, token.chainId) tokenCall.forEach((call) => { tokenCalls.push(call) }) }) const multiCallResult = await readContracts(wagmiConfig, { allowFailure: true, // eslint-disable-next-line @typescript-eslint/no-explicit-any contracts: tokenCalls as any, multicallAddress: MULTICALL_CONTRACT_ADDRESS, batchSize: 0, // 0 is unlimited }) const assetsWithBalance = [] for ( let i = 0; i < multiCallResult.length; i += MULTI_CALL_FUNCTIONS.length ) { const assetWithBalance = formatMultiCallResult( multiCallResult[i] as CallType, filteredByDeployedChains[i], ) assetsWithBalance.push(assetWithBalance) } return assetsWithBalance } catch (err) { console.error("[ERROR] on balances multiCall", err) return [] } } const formatMultiCallResult = ( call: CallType, selectedAsset: TAsset, ): TAsset => { try { const assetBalanceOf = call.result ? call.result : BigInt(0) const asset: TAsset = { ...selectedAsset, balanceOf: { decimals: selectedAsset.decimals, formatted: formatUnits(assetBalanceOf, selectedAsset.decimals), symbol: selectedAsset.name, value: assetBalanceOf, }, } return asset } catch (err) { console.error("[ERROR] on formatMultiCallResult", err) return { ...selectedAsset, prices: selectedAsset.prices || { default: 0 }, value: "0", // formatUnits(totalAssetValue, selectedAsset.decimals), balanceOf: { decimals: selectedAsset.decimals, formatted: "0.0", //formatUnits(assetBalanceOf, selectedAsset.decimals), symbol: selectedAsset.name, value: BigInt(0), // assetBalanceOf }, } } } /** * Fetches and filters asset balances for a given account address. * * @param {Address} accountAddress The address to fetch balances for. * @param {TAsset[]} assets An array of asset objects to fetch balances for. * @returns {Promise<TAsset[]>} A promise that resolves to an array of filtered asset objects. * @throws {Error} If there is an error fetching the asset balances. */ export const fetchAndFilterAssetBalances = async ( accountAddress: Address, assets: TAsset[], ): Promise<TAsset[]> => { try { const balances = await fetchAssetsBalanceMultiCall({ accountAddress: accountAddress as Address, assets: assets, }) const filteredUserAssets = balances.filter((asset) => { const isNonZeroBalance = !asset.balanceOf || asset.balanceOf.value !== BigInt(0) const containsScamWords = SCAM_TOKEN_WORDS.some((scamWord) => asset.name.toLowerCase().includes(scamWord), ) const containsScamAddresses = SCAM_TOKEN_ADDRESSES.includes( asset.address.toLowerCase(), ) return isNonZeroBalance && !containsScamWords && !containsScamAddresses }) // Filter out duplicate assets based on address const uniqueAssets = filteredUserAssets.reduce<TAsset[]>((acc, asset) => { if ( !acc.find( (a) => a.address.toLowerCase() === asset.address.toLowerCase(), ) ) { acc.push(asset) } return acc }, []) return sortAssets(uniqueAssets) } catch (err) { console.error("Error fetching and filtering asset balances:", err) throw err // Re-throw the error to be handled by the caller } }