@lifi/sdk
Version:
LI.FI Any-to-Any Cross-Chain-Swap SDK
106 lines • 3.99 kB
JavaScript
import { formatUnits } from 'viem';
import { ValidationError } from '../errors/errors.js';
import { SDKError } from '../errors/SDKError.js';
const parseBigInt = (value) => {
if (!value) {
return 0n;
}
try {
return BigInt(value);
}
catch {
return 0n;
}
};
const parseNumber = (value) => {
if (!value) {
return 0;
}
const parsed = Number(value);
return Number.isNaN(parsed) ? 0 : parsed;
};
const isZeroOutput = (toAmount, toAmountMin, toAmountUSD) => {
return (!parseBigInt(toAmount) &&
!parseBigInt(toAmountMin) &&
!parseNumber(toAmountUSD));
};
const hasNonZeroOutput = (step) => {
return (!!parseBigInt(step.estimate.toAmount) ||
!!parseBigInt(step.estimate.toAmountMin));
};
const findPreviousNonZeroStep = (steps) => {
// Find the last step that has non-zero output (the step before the zero output step)
for (let i = steps.length - 1; i >= 0; i--) {
const step = steps[i];
if (hasNonZeroOutput(step)) {
return step;
}
}
return undefined;
};
export function formatTokenPrice(amount, price, decimals) {
if (!amount || !price) {
return 0;
}
const formattedAmount = typeof amount === 'bigint' && decimals !== undefined
? formatUnits(amount, decimals)
: amount.toString();
if (Number.isNaN(Number(formattedAmount)) || Number.isNaN(Number(price))) {
return 0;
}
return Number.parseFloat(formattedAmount) * Number.parseFloat(price);
}
/**
* Converts a quote to Route
* @param quote - Step returned from the quote endpoint.
* @param options - Optional configuration for handling edge cases.
* @returns - The route to be executed.
* @throws {BaseError} Throws a ValidationError if the step has missing values.
*/
export const convertQuoteToRoute = (quote, options) => {
let toAmount = quote.estimate.toAmount;
let toAmountMin = quote.estimate.toAmountMin;
let toAmountUSD = quote.estimate.toAmountUSD;
// Handle zero output values by looking at previous included step
if (options?.adjustZeroOutputFromPreviousStep &&
quote.includedSteps?.length &&
isZeroOutput(toAmount, toAmountMin, toAmountUSD)) {
const previousStep = findPreviousNonZeroStep(quote.includedSteps);
if (previousStep) {
toAmount = previousStep.estimate.toAmount;
toAmountMin = previousStep.estimate.toAmountMin;
toAmountUSD = formatTokenPrice(parseBigInt(toAmount), previousStep.action.toToken.priceUSD, previousStep.action.toToken.decimals).toFixed(2);
// Update the last included step's estimate with the adjusted values
const lastStep = quote.includedSteps[quote.includedSteps.length - 1];
if (lastStep && !hasNonZeroOutput(lastStep)) {
lastStep.estimate.toAmount = toAmount;
lastStep.estimate.toAmountMin = toAmountMin;
}
}
}
if (!quote.estimate.fromAmountUSD) {
throw new SDKError(new ValidationError("Missing 'fromAmountUSD' in step estimate."));
}
if (!toAmountUSD) {
throw new SDKError(new ValidationError("Missing 'toAmountUSD' in step estimate."));
}
const route = {
id: quote.id,
fromChainId: quote.action.fromToken.chainId,
fromToken: quote.action.fromToken,
fromAmount: quote.action.fromAmount,
fromAmountUSD: quote.estimate.fromAmountUSD,
fromAddress: quote.action.fromAddress,
toChainId: quote.action.toToken.chainId,
toToken: quote.action.toToken,
toAmount,
toAmountMin,
toAmountUSD,
toAddress: quote.action.toAddress || quote.action.fromAddress,
gasCostUSD: quote.estimate.gasCosts?.[0]?.amountUSD || '0',
steps: [quote],
insurance: { state: 'NOT_INSURABLE', feeAmountUsd: '0' },
};
return route;
};
//# sourceMappingURL=convertQuoteToRoute.js.map