@arcana/ca-sdk
Version:
Arcana Network's chain abstraction SDK for unified balance in Web3 apps
146 lines (145 loc) • 5 kB
JavaScript
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();
}
}