@shogun-sdk/money-legos
Version:
Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.
162 lines (142 loc) • 5.12 kB
text/typescript
import { CHAIN_MAP, SOLANA_CHAIN_ID } from '../config/chains.js';
import type { TokenBalanceResults } from '../types/index.js';
import { NATIVE_TOKEN, SOL_NATIVE } from '../config/addresses.js';
import { buildInsufficientFundsWebAppError } from '../utils/errors.js';
import { fromWei } from '../utils/eth.js';
import { AffiliateFeeService } from './AffiliateFeeService.js';
export interface InsufficientFundsErrorBuilderProps {
nativeTotalBalance?: number;
sourceNetwork: number;
destinationNetwork: number;
isTransfer: boolean;
balances: TokenBalanceResults[];
tokenInAddress: string;
tokenOutAddress: string;
amountInput: string;
systemFeePercent: number;
errorBuilder?: (network: string, tokenSymbol?: string, minUsdValue?: number) => string;
isEoaAccount: boolean;
additionalAffiliateFee: number;
externalServiceTxCostUsd: number;
}
export class InsufficientFundsErrorBuilder {
private props: InsufficientFundsErrorBuilderProps;
constructor(props: InsufficientFundsErrorBuilderProps) {
this.props = props;
}
build(): string {
const {
balances,
nativeTotalBalance = 0,
sourceNetwork,
isTransfer,
amountInput,
tokenInAddress,
destinationNetwork,
systemFeePercent,
isEoaAccount,
additionalAffiliateFee,
externalServiceTxCostUsd,
errorBuilder = buildInsufficientFundsWebAppError,
} = this.props;
if (!nativeTotalBalance || nativeTotalBalance === 0) {
return errorBuilder(CHAIN_MAP[sourceNetwork]?.name ?? '');
}
// it is swap
const excludeFee = false /*excludeAffiliateFee({
srcToken: tokenInAddress,
destToken: tokenOutAddress,
srcChainId: sourceNetwork,
destChainId: destinationNetwork,
});*/
const sourceNativeUsd =
(sourceNetwork === SOLANA_CHAIN_ID
? balances.find((x) => x.tokenAddress === NATIVE_TOKEN.SOL && x.network === sourceNetwork)?.usdValue ||
balances.find((x) => x.tokenAddress === SOL_NATIVE && x.network === sourceNetwork)?.usdValue
: balances.find((x) => x.tokenAddress === NATIVE_TOKEN.ETH && x.network === sourceNetwork)?.usdValue) || 0;
const matchingToken = balances.find((x) => x.tokenAddress === tokenInAddress && x.network === sourceNetwork);
const sourceUsdValue = matchingToken ? Number(matchingToken?.balanceFormatted) * matchingToken?.usdPrice || 0 : 0;
const minUsdValue = AffiliateFeeService.calculateMinAllowedUsdValue(
systemFeePercent,
sourceNetwork,
excludeFee,
isEoaAccount,
externalServiceTxCostUsd,
isTransfer,
additionalAffiliateFee,
);
// if source and dest networks are the same, we need to check:
if (sourceNetwork === destinationNetwork) {
if (sourceNativeUsd < minUsdValue) {
return errorBuilder(CHAIN_MAP[sourceNetwork]?.name ?? '');
}
} else if (sourceUsdValue < minUsdValue) {
return errorBuilder(CHAIN_MAP[sourceNetwork]?.name ?? '');
}
return InsufficientFundsErrorBuilder.buildAffiliateFeeErrorMsg(
amountInput,
balances,
excludeFee,
tokenInAddress,
sourceNetwork,
isTransfer,
systemFeePercent,
isEoaAccount,
externalServiceTxCostUsd,
additionalAffiliateFee,
errorBuilder,
);
}
static buildAffiliateFeeErrorMsg(
amountInWei: string,
balances: TokenBalanceResults[],
excludeFee: boolean,
tokenInAddress: string,
sourceNetwork: number,
isTransfer: boolean,
systemFeePercent: number,
isEoaAccount: boolean,
externalServiceTxCostUsd: number,
additionalAffiliateFee: number,
errorBuilder: (network: string, tokenSymbol?: string, minAmountValue?: number, minUsdValue?: number) => string,
): string {
// We are not checking affiliate fee for transfers
if (isTransfer) {
return '';
}
const {
tokenUsdPrice,
decimals = 18,
symbol,
} = AffiliateFeeService.getAffiliateFeeTokenForQuote(balances, tokenInAddress, sourceNetwork) || {};
if (!tokenUsdPrice || !amountInWei || amountInWei === '0') {
return '';
}
const minUsdValue = AffiliateFeeService.calculateMinAllowedUsdValue(
systemFeePercent,
sourceNetwork,
excludeFee,
isEoaAccount,
externalServiceTxCostUsd,
isTransfer,
additionalAffiliateFee,
);
const amount = fromWei(amountInWei, decimals);
const amountUsd = +amount * tokenUsdPrice;
// Check for valid non-zero amount
if (amountUsd > 0 && amountUsd <= minUsdValue) {
// Prevent division by zero by checking amountUsd
const roundedAmountUsd = roundToBillion(amountUsd);
if (roundedAmountUsd === 0) {
return '';
}
const minAmountValue =
roundedAmountUsd > (Number(amount) * roundToBillion(minUsdValue)) / roundedAmountUsd ? 0 : amount;
return errorBuilder(CHAIN_MAP[sourceNetwork]?.name ?? '', symbol, Number(minAmountValue), minUsdValue);
}
return '';
}
}
const roundToBillion = (value: number) => {
return Math.round(value * 1000000000);
};