feat: refactor getDetailedRoute and add amount details to route info

This commit is contained in:
c4605
2025-03-17 18:17:36 +01:00
committed by Kyle
parent 15aea83cc5
commit 6562e486a6
3 changed files with 130 additions and 85 deletions

View File

@@ -1,25 +1,7 @@
import { Currency } from './currency';
import { runSpot, type TxToBroadCast } from './helpers/SwapHelper';
import { getLiquidityProviderFee } from './helpers/FeeHelper';
import {
deserializeAssetIdentifier,
type AlexSDKResponse,
type DetailedAMMRoutes as DetailedAMMRoute,
type DetailedAMMRoutes,
type PoolData,
type PriceData,
type StacksAssetContractAddress,
type TokenInfo,
} from './types';
import {
fetchBalanceForAccount,
getAlexSDKData,
getPrices,
} from './utils/fetchData';
import { getAllPossibleRoute } from './helpers/RouteHelper';
import { getYAmountFromXAmount } from './helpers/RateHelper';
import { fromEntries, isNotNull } from './utils/utils';
import type { AMMRoute } from './utils/ammRouteResolver';
import { getAllPossibleRoute, getDetailedRoute } from './helpers/RouteHelper';
import {
broadcastSponsoredTx,
getSponsorData,
@@ -28,8 +10,22 @@ import {
SponsoredTxError,
SponsoredTxErrorCode,
} from './helpers/SponsorTxHelper';
import { runSpot, type TxToBroadCast } from './helpers/SwapHelper';
import {
type AlexSDKResponse,
type DetailedAMMRoutes as DetailedAMMRoute,
type PoolData,
type PriceData,
type TokenInfo,
} from './types';
import type { AMMRoute } from './utils/ammRouteResolver';
import {
fetchBalanceForAccount,
getAlexSDKData,
getPrices,
} from './utils/fetchData';
import { props } from './utils/promiseHelpers';
import { announceAtLeastOne, hasLength } from './utils/arrayHelper';
import { fromEntries, isNotNull } from './utils/utils';
/**
* The AlexSDK class provides methods for interacting with a decentralized exchange (DEX) system,
@@ -113,16 +109,19 @@ export class AlexSDK {
*
* @param {Currency} from - The currency to swap from.
* @param {Currency} to - The currency to swap to.
* @param {bigint} fromAmount - The amount of the source currency to swap,
* if `fromAmount` is 0, the `toAmount` of the `DetailedAMMRoute` will be 0.
* @returns {Promise<DetailedAMMRoute[]>} - A promise that resolves to an array of DetailedAMMRoute objects,
* representing all possible swap routes between the two specified currencies with additional details.
*/
async getAllPossibleRoutesWithDetails(
from: Currency,
to: Currency
to: Currency,
fromAmount: bigint
): Promise<DetailedAMMRoute[]> {
const routes = await this.getAllPossibleRoutes(from, to);
const detailedRoutes = await Promise.all(
routes.map((r) => this.getDetailedRoute(r))
routes.map((r) => this.getDetailedRoute(r, fromAmount))
);
return detailedRoutes.filter(isNotNull);
}
@@ -131,6 +130,8 @@ export class AlexSDK {
* This function returns a detailed route for a given AMMRoute.
*
* @param {AMMRoute} route - The AMM route to get details for.
* @param {bigint} fromAmount - The amount of the source currency to swap,
* if `fromAmount` is 0, the `toAmount` of the `DetailedAMMRoute` will be 0.
* @returns {Promise<undefined | DetailedAMMRoute>} - A promise that resolves to a DetailedAMMRoute object
* if the route details can be fetched, or undefined if any token information is missing.
*
@@ -139,70 +140,18 @@ export class AlexSDK {
* currencies, token addresses, and pool details for each step in the route.
*/
async getDetailedRoute(
route: AMMRoute
route: AMMRoute,
fromAmount: bigint
): Promise<undefined | DetailedAMMRoute> {
const detailRoute = await Promise.all(
route.map(
async (
segment
): Promise<
| undefined
| (DetailedAMMRoutes['swapPools'][number] & {
fromCurrency: Currency;
fromTokenAddress: StacksAssetContractAddress;
})
> => {
const [fromTokenInfo, toTokenInfo] = await Promise.all([
this.fetchTokenInfo(segment.from),
this.fetchTokenInfo(segment.neighbour),
]);
if (fromTokenInfo == null || toTokenInfo == null) {
return undefined;
}
const fromTokenAddress = deserializeAssetIdentifier(
fromTokenInfo.wrapToken
);
const toTokenAddress = deserializeAssetIdentifier(
toTokenInfo.wrapToken
);
if (fromTokenAddress == null || toTokenAddress == null) {
return undefined;
}
return {
fromCurrency: segment.from,
fromTokenAddress,
toCurrency: segment.neighbour,
toTokenAddress,
poolId: segment.pool.poolId,
pool: segment.pool,
};
}
)
return getDetailedRoute(
await props({
ammPools: this.getPools(),
getContractId: this.getContractId(),
tokenInfoMappings: this.getTokenMappings(),
}),
route,
fromAmount
);
if (detailRoute.some((x) => x == null)) return undefined;
const _detailRoute = detailRoute as NonNullable<
(typeof detailRoute)[number]
>[];
if (hasLength(_detailRoute, 0)) return undefined;
const firstSegment = _detailRoute[0];
return {
fromCurrency: firstSegment.fromCurrency,
fromTokenAddress: firstSegment.fromTokenAddress,
swapPools: announceAtLeastOne(
_detailRoute.map((segment) => ({
toCurrency: segment.toCurrency,
toTokenAddress: segment.toTokenAddress,
poolId: segment.poolId,
pool: segment.pool,
}))
),
};
}
/**

View File

@@ -1,9 +1,17 @@
import type { Currency } from '../currency';
import type { PoolData } from '../types';
import type {
PoolData,
DetailedAMMRoutes,
StacksAssetContractAddress,
} from '../types';
import {
type AMMRouteSegment,
resolveAmmRoutes,
} from '../utils/ammRouteResolver';
import { deserializeAssetIdentifier } from '../types';
import { announceAtLeastOne, hasLength } from '../utils/arrayHelper';
import type { TokenInfo } from '../types';
import { getYAmountFromXAmount } from './RateHelper';
export async function getAllPossibleRoute(
from: Currency,
@@ -12,3 +20,89 @@ export async function getAllPossibleRoute(
): Promise<AMMRouteSegment[][]> {
return resolveAmmRoutes(from, to, pools);
}
export async function getDetailedRoute(
context: {
ammPools: PoolData[];
tokenInfoMappings: Record<Currency, TokenInfo>;
getContractId: (currency: Currency) => string;
},
route: AMMRouteSegment[],
fromAmount: bigint
): Promise<undefined | DetailedAMMRoutes> {
const detailRoute = await Promise.all(
route.map(
async (
segment
): Promise<
| undefined
| (DetailedAMMRoutes['swapPools'][number] & {
fromCurrency: Currency;
fromTokenAddress: StacksAssetContractAddress;
})
> => {
const fromTokenInfo = context.tokenInfoMappings[segment.from];
const toTokenInfo = context.tokenInfoMappings[segment.neighbour];
if (fromTokenInfo == null || toTokenInfo == null) {
return undefined;
}
const fromTokenAddress = deserializeAssetIdentifier(
fromTokenInfo.wrapToken
);
const toTokenAddress = deserializeAssetIdentifier(
toTokenInfo.wrapToken
);
if (fromTokenAddress == null || toTokenAddress == null) {
return undefined;
}
return {
fromCurrency: segment.from,
fromTokenAddress,
toCurrency: segment.neighbour,
toTokenAddress,
poolId: segment.pool.poolId,
pool: segment.pool,
};
}
)
);
if (detailRoute.some((x) => x == null)) return undefined;
const _detailRoute = detailRoute as NonNullable<
(typeof detailRoute)[number]
>[];
if (hasLength(_detailRoute, 0)) return undefined;
const firstSegment = _detailRoute[0];
const lastSegment = _detailRoute[_detailRoute.length - 1];
const toAmount =
fromAmount === BigInt(0)
? BigInt(0)
: await getYAmountFromXAmount(
firstSegment.fromCurrency,
lastSegment.toCurrency,
fromAmount,
context.ammPools,
context.getContractId,
route
).catch(() => BigInt(0));
return {
fromCurrency: firstSegment.fromCurrency,
fromTokenAddress: firstSegment.fromTokenAddress,
swapPools: announceAtLeastOne(
_detailRoute.map((segment) => ({
toCurrency: segment.toCurrency,
toTokenAddress: segment.toTokenAddress,
poolId: segment.poolId,
pool: segment.pool,
}))
),
fromAmount,
toAmount,
};
}

View File

@@ -104,4 +104,6 @@ export type DetailedAMMRoutes = {
poolId: bigint;
pool: PoolData;
}>;
fromAmount: bigint;
toAmount: bigint;
};