UNPKG

@arcana/ca-sdk

Version:

Arcana Network's chain abstraction SDK for unified balance in Web3 apps

146 lines (145 loc) 5 kB
import { Universe, } from "@arcana/ca-common"; import Decimal from "decimal.js"; import { CHAIN_IDS, } from "fuels"; import { FUEL_BASE_ASSET_ID, ZERO_ADDRESS } from "./constants"; import { getLogger } from "./logger"; import { equalFold } from "./utils"; export class UserAsset { get balance() { return this.value.balance; } constructor(value) { this.value = value; } getBalanceOnChain(chainID, tokenAddress) { return (this.value.breakdown.find((b) => { if (tokenAddress) { return (b.chain.id === chainID && equalFold(b.contractAddress, tokenAddress)); } return b.chain.id === chainID; })?.balance ?? "0"); } isDeposit(tokenAddress, universe) { if (universe === Universe.ETHEREUM) { return equalFold(tokenAddress, ZERO_ADDRESS); } if (universe === Universe.FUEL) { return true; } return false; } iterate(feeStore) { return this.value.breakdown .filter((b) => new Decimal(b.balance).greaterThan(0)) .sort((a, b) => { if (a.chain.id === 1) { return 1; } if (b.chain.id === 1) { return -1; } return Decimal.sub(b.balance, a.balance).toNumber(); }) .map((b) => { let balance = new Decimal(b.balance); if (this.isDeposit(b.contractAddress, b.universe)) { const collectionFee = feeStore.calculateCollectionFee({ decimals: b.decimals, sourceChainID: b.chain.id, sourceTokenAddress: b.contractAddress, }); let estimatedGasForDeposit = collectionFee.mul(b.chain.id === 1 ? 2 : 4); if (b.contractAddress === FUEL_BASE_ASSET_ID && b.chain.id === CHAIN_IDS.fuel.mainnet) { // Estimating this amount of gas is required for fuel -> vault estimatedGasForDeposit = new Decimal("0.000_003"); } if (new Decimal(b.balance).lessThan(estimatedGasForDeposit)) { balance = new Decimal(0); } else { balance = new Decimal(b.balance).minus(estimatedGasForDeposit); } } return { balance, chainID: b.chain.id, decimals: b.decimals, tokenContract: b.contractAddress, universe: b.universe, }; }); } } export class UserAssets { constructor(data) { this.data = data; } add(asset) { this.data.push(asset); } find(symbol) { for (const asset of this.data) { if (equalFold(asset.symbol, symbol)) { return new UserAsset(asset); } } throw new Error("Asset is not supported."); } findOnChain(chainID, address) { return this.data.find((asset) => { const index = asset.breakdown.findIndex((b) => b.chain.id === chainID && equalFold(b.contractAddress, address)); if (index > -1) { return asset; } return null; }); } getAssetDetails(chain, address) { const asset = this.findOnChain(chain.id, address); if (!asset) { throw new Error("Asset is not supported."); } getLogger().debug("getAssetDetails", { asset, assets: this.data, }); const destinationGasBalance = this.getNativeBalance(chain); const chainsWithBalance = this.getChainCountWithBalance(asset); const destinationAssetBalance = asset.breakdown.find((b) => b.chain.id === chain.id)?.balance ?? "0"; return { asset, chainsWithBalance, destinationAssetBalance, destinationGasBalance, }; } getBalanceInFiat() { return this.data .reduce((total, asset) => { return total.add(asset.balanceInFiat); }, new Decimal(0)) .toDecimalPlaces(2) .toNumber(); } getChainCountWithBalance(asset) { return (asset?.breakdown.filter((b) => new Decimal(b.balance).greaterThan(0)) .length ?? 0); } getNativeBalance(chain) { const asset = this.data.find((a) => equalFold(a.symbol, chain.nativeCurrency.symbol)); if (asset) { return (asset.breakdown.find((b) => b.chain.id === chain.id)?.balance ?? "0"); } return "0"; } sort() { this.data.forEach((asset) => { asset.breakdown.sort((a, b) => b.balanceInFiat - a.balanceInFiat); }); this.data.sort((a, b) => b.balanceInFiat - a.balanceInFiat); } [Symbol.iterator]() { return this.data.values(); } }