From 1d4a438ea60edafed57f9264a0f70d42f1522d68 Mon Sep 17 00:00:00 2001 From: Kozer4 Date: Wed, 22 Nov 2023 14:16:57 +0200 Subject: [PATCH] feat(sdk): add getPendingStatusInfo method --- src/client/core-api/api-client-caching.ts | 14 + src/client/core-api/api-client.ts | 7 + src/client/core-api/core-api.model.ts | 8 + .../core-api/core-client-pool-info-caching.ts | 5 + src/client/core-api/index.ts | 7 + src/index.ts | 358 +++---------- src/models/index.ts | 19 + src/services/bridge/index.ts | 11 +- .../bridge/raw-bridge-transaction-builder.ts | 2 +- src/services/index.ts | 497 ++++++++++++++++++ src/services/liquidity-pool/index.ts | 3 +- .../raw-pool-transaction-builder.ts | 2 +- src/services/token/index.ts | 3 +- src/utils/calculation/index.ts | 25 + src/utils/index.ts | 4 +- 15 files changed, 669 insertions(+), 296 deletions(-) create mode 100644 src/services/index.ts diff --git a/src/client/core-api/api-client-caching.ts b/src/client/core-api/api-client-caching.ts index fcd4f4d2..f7ef3307 100644 --- a/src/client/core-api/api-client-caching.ts +++ b/src/client/core-api/api-client-caching.ts @@ -3,6 +3,7 @@ import { ChainSymbol } from "../../chains"; import { PoolInfoMap, PoolKeyObject } from "../../tokens-info"; import { ApiClient, TokenInfo } from "./api-client"; import { + PendingInfoResponse, ReceiveTransactionCostRequest, ReceiveTransactionCostResponse, TransferStatusResponse, @@ -13,11 +14,13 @@ const _55_SECONDS_TTL = 55 * 1000; export class ApiClientCaching implements ApiClient { private tokenInfoCache: Cache>; + private pendingInfoCache: Cache>; private receivedTransactionCache: Cache; constructor(private apiClient: ApiClient) { this.tokenInfoCache = new Cache>({ defaultTtl: _55_SECONDS_TTL }); this.receivedTransactionCache = new Cache({ defaultTtl: _20_SECONDS_TTL }); + this.pendingInfoCache = new Cache>({ defaultTtl: _20_SECONDS_TTL }); } getTokenInfo(): Promise { @@ -31,6 +34,17 @@ export class ApiClientCaching implements ApiClient { return tokenInfoPromise; } + async getPendingInfo(): Promise { + const PENDING_INFO_CACHE_KEY = "PENDING_INFO_CACHE_KEY"; + const pendingInfo = this.pendingInfoCache.get(PENDING_INFO_CACHE_KEY); + if (pendingInfo) { + return pendingInfo; + } + const pendingInfoPromise = this.apiClient.getPendingInfo(); + this.pendingInfoCache.put(PENDING_INFO_CACHE_KEY, pendingInfoPromise); + return pendingInfoPromise; + } + async getReceiveTransactionCost(args: ReceiveTransactionCostRequest): Promise { const RECEIVE_TX_COST_KEY = `RECEIVE_TX_COST_${args.sourceChainId}_${args.destinationChainId}_${args.messenger}`; const transactionCost = this.receivedTransactionCache.get(RECEIVE_TX_COST_KEY); diff --git a/src/client/core-api/api-client.ts b/src/client/core-api/api-client.ts index e56e319d..4809a975 100644 --- a/src/client/core-api/api-client.ts +++ b/src/client/core-api/api-client.ts @@ -9,6 +9,7 @@ import { } from "./core-api-mapper"; import { ChainDetailsResponse, + PendingInfoResponse, PoolInfoResponse, ReceiveTransactionCostRequest, ReceiveTransactionCostResponse, @@ -23,6 +24,7 @@ export interface TokenInfo { export interface ApiClient { getTokenInfo(): Promise; + getPendingInfo(): Promise; getTransferStatus(chainSymbol: ChainSymbol, txId: string): Promise; getReceiveTransactionCost(args: ReceiveTransactionCostRequest): Promise; getPoolInfoMap(pools: PoolKeyObject[] | PoolKeyObject): Promise; @@ -51,6 +53,11 @@ export class ApiClientImpl implements ApiClient { }; } + async getPendingInfo(): Promise { + const { data } = await this.api.get("/pending-info"); + return data; + } + async getTransferStatus(chainSymbol: ChainSymbol, txId: string): Promise { const { data } = await this.api.get(`/chain/${chainSymbol}/${txId}`); return data; diff --git a/src/client/core-api/core-api.model.ts b/src/client/core-api/core-api.model.ts index 2716a170..58cf3e6d 100644 --- a/src/client/core-api/core-api.model.ts +++ b/src/client/core-api/core-api.model.ts @@ -136,3 +136,11 @@ export type PoolInfoResponse = { string: PoolInfo; }; }; + +export type PendingInfoResponse = Record; +export type TokenPendingInfoDTO = Record; + +export interface PendingInfoDTO { + pendingTxs: number; + totalSentAmount: string; +} diff --git a/src/client/core-api/core-client-pool-info-caching.ts b/src/client/core-api/core-client-pool-info-caching.ts index 67e1f3c3..3c70b525 100644 --- a/src/client/core-api/core-client-pool-info-caching.ts +++ b/src/client/core-api/core-client-pool-info-caching.ts @@ -3,6 +3,7 @@ import { ChainSymbol } from "../../chains"; import { ChainDetailsMap, PoolInfo, PoolInfoMap, PoolKeyObject, TokenWithChainDetails } from "../../tokens-info"; import { mapChainDetailsMapToPoolKeyObjects, mapPoolKeyObjectToPoolKey } from "./core-api-mapper"; import { + PendingInfoResponse, ReceiveTransactionCostRequest, ReceiveTransactionCostResponse, TransferStatusResponse, @@ -34,6 +35,10 @@ export class AllbridgeCoreClientPoolInfoCaching implements AllbridgeCoreClient { return this.client.getReceiveTransactionCost(args); } + getPendingInfo(): Promise { + return this.client.getPendingInfo(); + } + async getPoolInfoByKey(poolKeyObject: PoolKeyObject): Promise { this.poolInfoCache.putAllIfNotExists((await this.client.getChainDetailsMapAndPoolInfoMap()).poolInfoMap); const poolInfo = this.poolInfoCache.get(poolKeyObject); diff --git a/src/client/core-api/index.ts b/src/client/core-api/index.ts index ad6708e4..5450e9bf 100644 --- a/src/client/core-api/index.ts +++ b/src/client/core-api/index.ts @@ -2,6 +2,7 @@ import { ChainSymbol } from "../../chains"; import { ChainDetailsMap, PoolInfoMap, PoolKeyObject, TokenWithChainDetails } from "../../tokens-info"; import { ApiClient } from "./api-client"; import { + PendingInfoResponse, ReceiveTransactionCostRequest, ReceiveTransactionCostResponse, TransferStatusResponse, @@ -17,6 +18,8 @@ export interface AllbridgeCoreClient { getChainDetailsMap(): Promise; tokens(): Promise; + getPendingInfo(): Promise; + getTransferStatus(chainSymbol: ChainSymbol, txId: string): Promise; getReceiveTransactionCost(args: ReceiveTransactionCostRequest): Promise; @@ -34,6 +37,10 @@ export class AllbridgeCoreClientImpl implements AllbridgeCoreClient { return Object.values(map).flatMap((chainDetails) => chainDetails.tokens); } + async getPendingInfo(): Promise { + return this.apiClient.getPendingInfo(); + } + async getChainDetailsMapAndPoolInfoMap(): Promise<{ chainDetailsMap: ChainDetailsMap; poolInfoMap: PoolInfoMap; diff --git a/src/index.ts b/src/index.ts index 32ff4636..7b98158a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,49 +1,27 @@ -import { Big } from "big.js"; -import { ChainSymbol } from "./chains"; -import { AllbridgeCoreClientImpl } from "./client/core-api"; -import { ApiClientImpl } from "./client/core-api/api-client"; -import { ApiClientCaching } from "./client/core-api/api-client-caching"; -import { TransferStatusResponse } from "./client/core-api/core-api.model"; -import { AllbridgeCoreClientPoolInfoCaching } from "./client/core-api/core-client-pool-info-caching"; +import { BigSource } from "big.js"; import { mainnet } from "./configs"; -import { CCTPDoesNotSupportedError, InsufficientPoolLiquidityError, NodeRpcUrlNotInitializedError } from "./exceptions"; import { AmountFormat, AmountFormatted, AmountsAndGasFeeOptions, + BridgeService, + ChainDetailsMap, + ChainSymbol, ExtraGasMaxLimitResponse, GasFeeOptions, + GetNativeTokenBalanceParams, GetTokenBalanceParams, + LiquidityPoolService, Messenger, -} from "./models"; -import { BridgeService, DefaultBridgeService } from "./services/bridge"; -import { GetNativeTokenBalanceParams } from "./services/bridge/models"; -import { SolanaBridgeParams } from "./services/bridge/sol"; -import { getExtraGasMaxLimits, getGasFeeOptions } from "./services/bridge/utils"; -import { DefaultLiquidityPoolService, LiquidityPoolService } from "./services/liquidity-pool"; -import { Provider } from "./services/models"; -import { DefaultTokenService, TokenService } from "./services/token"; -import { ChainDetailsMap, PoolInfo, PoolKeyObject, TokenWithChainDetails } from "./tokens-info"; -import { getPoolInfoByToken, validateAmountDecimals, validateAmountGtZero } from "./utils"; -import { - aprInPercents, - convertAmountPrecision, - convertFloatAmountToInt, - convertIntAmountToFloat, - fromSystemPrecision, - getFeePercent, - swapFromVUsd, - swapFromVUsdReverse, - swapToVUsd, - swapToVUsdReverse, -} from "./utils/calculation"; -import { SYSTEM_PRECISION } from "./utils/calculation/constants"; -import { SendAmountDetails, getSendAmountDetails } from "./utils/calculation/swap-and-bridge-details"; -import { + PendingStatusInfoResponse, + PoolInfo, + Provider, + SendAmountDetails, SwapAndBridgeCalculationData, - swapAndBridgeFeeCalculation, - swapAndBridgeFeeCalculationReverse, -} from "./utils/calculation/swap-and-bridge-fee-calc"; + TokenWithChainDetails, + TransferStatusResponse, +} from "./models"; +import { AllbridgeCoreSdkService, NodeRpcUrlsConfig } from "./services"; export * from "./configs"; export * from "./models"; @@ -71,19 +49,6 @@ export type NodeRpcUrls = { [key in ChainSymbol]?: string; }; -export class NodeRpcUrlsConfig { - constructor(private nodeRpcUrls: NodeRpcUrls) {} - - getNodeRpcUrl(chainSymbol: ChainSymbol): string { - const nodeRpcUrl = this.nodeRpcUrls[chainSymbol]; - if (nodeRpcUrl !== undefined) { - return nodeRpcUrl; - } else { - throw new NodeRpcUrlNotInitializedError(chainSymbol); - } - } -} - /** * @Deprecated Use {@link NodeRpcUrls} */ @@ -100,20 +65,13 @@ function isNodeUrlsConfig(nodeUrls: NodeUrlsConfig | NodeRpcUrls): nodeUrls is N } export class AllbridgeCoreSdk { - /** - * @internal - */ - private readonly api: AllbridgeCoreClientPoolInfoCaching; - /** - * @internal - */ - private readonly tokenService: TokenService; - readonly params: AllbridgeCoreSdkOptions; bridge: BridgeService; pool: LiquidityPoolService; + private service: AllbridgeCoreSdkService; + /** * Initializes the SDK object. * @param nodeUrls node rpc urls for full functionality @@ -128,19 +86,9 @@ export class AllbridgeCoreSdk { } else { nodeRpcUrlsConfig = new NodeRpcUrlsConfig(nodeUrls); } - - const apiClient = new ApiClientImpl(params); - const apiClientTokenInfoCaching = new ApiClientCaching(apiClient); - const coreClient = new AllbridgeCoreClientImpl(apiClientTokenInfoCaching); - this.api = new AllbridgeCoreClientPoolInfoCaching(coreClient); - - const solBridgeParams: SolanaBridgeParams = { - wormholeMessengerProgramId: params.wormholeMessengerProgramId, - solanaLookUpTable: params.solanaLookUpTable, - }; - this.tokenService = new DefaultTokenService(this.api, nodeRpcUrlsConfig); - this.bridge = new DefaultBridgeService(this.api, nodeRpcUrlsConfig, solBridgeParams, this.tokenService); - this.pool = new DefaultLiquidityPoolService(this.api, nodeRpcUrlsConfig, this.tokenService); + this.service = new AllbridgeCoreSdkService(nodeRpcUrlsConfig, params); + this.bridge = this.service.bridge; + this.pool = this.service.pool; this.params = params; } @@ -148,22 +96,21 @@ export class AllbridgeCoreSdk { * Returns {@link ChainDetailsMap} containing a list of supported tokens groped by chain. */ async chainDetailsMap(): Promise { - return this.api.getChainDetailsMap(); + return this.service.chainDetailsMap(); } /** * Returns a list of supported {@link TokenWithChainDetails | tokens}. */ async tokens(): Promise { - return this.api.tokens(); + return this.service.tokens(); } /** * Returns a list of supported {@link TokenWithChainDetails | tokens} on the selected chain. */ async tokensByChain(chainSymbol: ChainSymbol): Promise { - const map = await this.api.getChainDetailsMap(); - return map[chainSymbol].tokens; + return this.service.tokensByChain(chainSymbol); } /** @@ -172,7 +119,24 @@ export class AllbridgeCoreSdk { * @param txId */ async getTransferStatus(chainSymbol: ChainSymbol, txId: string): Promise { - return this.api.getTransferStatus(chainSymbol, txId); + return this.service.getTransferStatus(chainSymbol, txId); + } + + /** + * Returns information about pending transactions for the same destination chain and the amount of tokens can be received as a result of transfer considering pending transactions. + * @param amount the amount of tokens that will be sent + * @param amountFormat amount format + * @param sourceToken selected token transfer from + * @param destToken selected token transfer to + * @returns range of possible amount based on already pending transactions + */ + async getPendingStatusInfo( + amount: string, + amountFormat: AmountFormat, + sourceToken: TokenWithChainDetails, + destToken: TokenWithChainDetails + ): Promise { + return this.service.getPendingStatusInfo(amount, amountFormat, sourceToken, destToken); } /** @@ -182,7 +146,7 @@ export class AllbridgeCoreSdk { * @returns Token balance */ async getTokenBalance(params: GetTokenBalanceParams, provider?: Provider): Promise { - return this.tokenService.getTokenBalance(params, provider); + return this.service.getTokenBalance(params, provider); } /** @@ -192,7 +156,7 @@ export class AllbridgeCoreSdk { * @returns Token balance */ async getNativeTokenBalance(params: GetNativeTokenBalanceParams, provider?: Provider): Promise { - return this.tokenService.getNativeTokenBalance(params, provider); + return this.service.getNativeTokenBalance(params, provider); } /** @@ -204,22 +168,10 @@ export class AllbridgeCoreSdk { * @returns fee percent */ async calculateFeePercentOnSourceChain( - amountFloat: number | string | Big, + amountFloat: BigSource, sourceChainToken: TokenWithChainDetails ): Promise { - validateAmountGtZero(amountFloat); - validateAmountDecimals("amountFloat", amountFloat, sourceChainToken.decimals); - const amountInt = convertFloatAmountToInt(amountFloat, sourceChainToken.decimals); - if (amountInt.eq(0)) { - return 0; - } - const vUsdInSystemPrecision = swapToVUsd( - amountInt, - sourceChainToken, - await getPoolInfoByToken(this.api, sourceChainToken) - ); - const vUsdInSourcePrecision = fromSystemPrecision(vUsdInSystemPrecision, sourceChainToken.decimals); - return getFeePercent(amountInt, vUsdInSourcePrecision); + return this.service.calculateFeePercentOnSourceChain(amountFloat, sourceChainToken); } /** @@ -233,28 +185,11 @@ export class AllbridgeCoreSdk { * @returns fee percent */ async calculateFeePercentOnDestinationChain( - amountFloat: number | string | Big, + amountFloat: BigSource, sourceChainToken: TokenWithChainDetails, destinationChainToken: TokenWithChainDetails ): Promise { - validateAmountGtZero(amountFloat); - validateAmountDecimals("amountFloat", amountFloat, sourceChainToken.decimals); - const amountInt = convertFloatAmountToInt(amountFloat, sourceChainToken.decimals); - if (amountInt.eq(0)) { - return 0; - } - const vUsdInSystemPrecision = swapToVUsd( - amountInt, - sourceChainToken, - await getPoolInfoByToken(this.api, sourceChainToken) - ); - const usd = swapFromVUsd( - vUsdInSystemPrecision, - destinationChainToken, - await getPoolInfoByToken(this.api, destinationChainToken) - ); - const vUsdInDestinationPrecision = fromSystemPrecision(vUsdInSystemPrecision, destinationChainToken.decimals); - return getFeePercent(vUsdInDestinationPrecision, usd); + return this.service.calculateFeePercentOnDestinationChain(amountFloat, sourceChainToken, destinationChainToken); } /** @@ -266,23 +201,17 @@ export class AllbridgeCoreSdk { * @param messenger */ async getAmountToBeReceivedAndGasFeeOptions( - amountToSendFloat: number | string | Big, + amountToSendFloat: BigSource, sourceChainToken: TokenWithChainDetails, destinationChainToken: TokenWithChainDetails, messenger: Messenger ): Promise { - validateAmountGtZero(amountToSendFloat); - validateAmountDecimals("amountToSendFloat", amountToSendFloat, sourceChainToken.decimals); - return { - amountToSendFloat: Big(amountToSendFloat).toFixed(), - amountToBeReceivedFloat: await this.getAmountToBeReceived( - amountToSendFloat, - sourceChainToken, - destinationChainToken, - messenger - ), - gasFeeOptions: await this.getGasFeeOptions(sourceChainToken, destinationChainToken, messenger), - }; + return this.service.getAmountToBeReceivedAndGasFeeOptions( + amountToSendFloat, + sourceChainToken, + destinationChainToken, + messenger + ); } /** @@ -294,23 +223,17 @@ export class AllbridgeCoreSdk { * @param messenger */ async getAmountToSendAndGasFeeOptions( - amountToBeReceivedFloat: number | string | Big, + amountToBeReceivedFloat: BigSource, sourceChainToken: TokenWithChainDetails, destinationChainToken: TokenWithChainDetails, messenger: Messenger ): Promise { - validateAmountGtZero(amountToBeReceivedFloat); - validateAmountDecimals("amountToBeReceivedFloat", amountToBeReceivedFloat, destinationChainToken.decimals); - return { - amountToSendFloat: await this.getAmountToSend( - amountToBeReceivedFloat, - sourceChainToken, - destinationChainToken, - messenger - ), - amountToBeReceivedFloat: Big(amountToBeReceivedFloat).toFixed(), - gasFeeOptions: await this.getGasFeeOptions(sourceChainToken, destinationChainToken, messenger), - }; + return this.service.getAmountToSendAndGasFeeOptions( + amountToBeReceivedFloat, + sourceChainToken, + destinationChainToken, + messenger + ); } /** @@ -321,7 +244,7 @@ export class AllbridgeCoreSdk { * @param messenger Optional. selected messenger */ async getAmountToBeReceived( - amountToSendFloat: number | string | Big, + amountToSendFloat: BigSource, sourceChainToken: TokenWithChainDetails, destinationChainToken: TokenWithChainDetails, /** @@ -331,33 +254,7 @@ export class AllbridgeCoreSdk { */ messenger?: Messenger ): Promise { - validateAmountGtZero(amountToSendFloat); - validateAmountDecimals("amountToSendFloat", amountToSendFloat, sourceChainToken.decimals); - const amountToSend = convertFloatAmountToInt(amountToSendFloat, sourceChainToken.decimals); - - if (messenger && messenger == Messenger.CCTP) { - if (!sourceChainToken.cctpAddress || !destinationChainToken.cctpAddress || !sourceChainToken.cctpFeeShare) { - throw new CCTPDoesNotSupportedError("Such route does not support CCTP protocol"); - } - const result = amountToSend.mul(Big(1).minus(sourceChainToken.cctpFeeShare)).round(0, Big.roundUp); - const resultInDestPrecision = convertAmountPrecision( - result, - sourceChainToken.decimals, - destinationChainToken.decimals - ).round(0); - return convertIntAmountToFloat(resultInDestPrecision, destinationChainToken.decimals).toFixed(); - } - - const vUsd = swapToVUsd(amountToSend, sourceChainToken, await getPoolInfoByToken(this.api, sourceChainToken)); - const resultInt = swapFromVUsd( - vUsd, - destinationChainToken, - await getPoolInfoByToken(this.api, destinationChainToken) - ); - if (Big(resultInt).lt(0)) { - throw new InsufficientPoolLiquidityError(); - } - return convertIntAmountToFloat(resultInt, destinationChainToken.decimals).toFixed(); + return this.service.getAmountToBeReceived(amountToSendFloat, sourceChainToken, destinationChainToken, messenger); } /** @@ -368,7 +265,7 @@ export class AllbridgeCoreSdk { * @param messenger Optional. selected messenger */ async getAmountToSend( - amountToBeReceivedFloat: number | string | Big, + amountToBeReceivedFloat: BigSource, sourceChainToken: TokenWithChainDetails, destinationChainToken: TokenWithChainDetails, /** @@ -378,33 +275,7 @@ export class AllbridgeCoreSdk { */ messenger?: Messenger ): Promise { - validateAmountGtZero(amountToBeReceivedFloat); - validateAmountDecimals("amountToBeReceivedFloat", amountToBeReceivedFloat, destinationChainToken.decimals); - const amountToBeReceived = convertFloatAmountToInt(amountToBeReceivedFloat, destinationChainToken.decimals); - - if (messenger && messenger == Messenger.CCTP) { - if (!sourceChainToken.cctpAddress || !destinationChainToken.cctpAddress || !sourceChainToken.cctpFeeShare) { - throw new CCTPDoesNotSupportedError("Such route does not support CCTP protocol"); - } - const result = amountToBeReceived.div(Big(1).minus(sourceChainToken.cctpFeeShare)).round(0, Big.roundDown); - const resultInSourcePrecision = convertAmountPrecision( - result, - destinationChainToken.decimals, - sourceChainToken.decimals - ).round(0); - return convertIntAmountToFloat(resultInSourcePrecision, sourceChainToken.decimals).toFixed(); - } - - const vUsd = swapFromVUsdReverse( - amountToBeReceived, - destinationChainToken, - await getPoolInfoByToken(this.api, destinationChainToken) - ); - const resultInt = swapToVUsdReverse(vUsd, sourceChainToken, await getPoolInfoByToken(this.api, sourceChainToken)); - if (Big(resultInt).lte(0)) { - throw new InsufficientPoolLiquidityError(); - } - return convertIntAmountToFloat(resultInt, sourceChainToken.decimals).toFixed(); + return this.service.getAmountToSend(amountToBeReceivedFloat, sourceChainToken, destinationChainToken, messenger); } /** @@ -419,14 +290,7 @@ export class AllbridgeCoreSdk { destinationChainToken: TokenWithChainDetails, messenger: Messenger ): Promise { - return getGasFeeOptions( - sourceChainToken.allbridgeChainId, - sourceChainToken.chainType, - destinationChainToken.allbridgeChainId, - sourceChainToken.decimals, - messenger, - this.api - ); + return this.service.getGasFeeOptions(sourceChainToken, destinationChainToken, messenger); } /** @@ -441,10 +305,7 @@ export class AllbridgeCoreSdk { destinationChainToken: TokenWithChainDetails, messenger: Messenger ): number | null { - return ( - /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */ - sourceChainToken.transferTime?.[destinationChainToken.chainSymbol]?.[messenger] ?? null - ); + return this.service.getAverageTransferTime(sourceChainToken, destinationChainToken, messenger); } /** @@ -453,7 +314,7 @@ export class AllbridgeCoreSdk { * @returns poolInfo */ async getPoolInfoByToken(token: TokenWithChainDetails): Promise { - return await this.api.getPoolInfoByKey({ chainSymbol: token.chainSymbol, poolAddress: token.poolAddress }); + return this.service.getPoolInfoByToken(token); } /** @@ -464,14 +325,7 @@ export class AllbridgeCoreSdk { * @param tokens if present, the corresponding liquidity pools will be updated */ async refreshPoolInfo(tokens?: TokenWithChainDetails | TokenWithChainDetails[]): Promise { - if (tokens) { - const tokensArray = tokens instanceof Array ? tokens : [tokens]; - const poolKeys: PoolKeyObject[] = tokensArray.map((t) => { - return { chainSymbol: t.chainSymbol, poolAddress: t.poolAddress }; - }); - return this.api.refreshPoolInfo(poolKeys); - } - return this.api.refreshPoolInfo(); + return this.service.refreshPoolInfo(tokens); } /** @@ -480,7 +334,7 @@ export class AllbridgeCoreSdk { * @returns aprPercentageView */ aprInPercents(apr: number): string { - return aprInPercents(apr); + return this.service.aprInPercents(apr); } /** @@ -493,7 +347,7 @@ export class AllbridgeCoreSdk { sourceChainToken: TokenWithChainDetails, destinationChainToken: TokenWithChainDetails ): Promise { - return await getExtraGasMaxLimits(sourceChainToken, destinationChainToken, this.api); + return this.service.getExtraGasMaxLimits(sourceChainToken, destinationChainToken); } /** @@ -507,20 +361,7 @@ export class AllbridgeCoreSdk { amountFormat: AmountFormat, sourceToken: TokenWithChainDetails ): Promise { - validateAmountGtZero(amount); - let amountInTokenPrecision; - if (amountFormat == AmountFormat.FLOAT) { - validateAmountDecimals("amount", amount, sourceToken.decimals); - amountInTokenPrecision = convertFloatAmountToInt(amount, sourceToken.decimals).toFixed(); - } else { - amountInTokenPrecision = amount; - } - - const vUsdAmount = swapToVUsd(amountInTokenPrecision, sourceToken, await getPoolInfoByToken(this.api, sourceToken)); - return { - [AmountFormat.INT]: vUsdAmount, - [AmountFormat.FLOAT]: convertIntAmountToFloat(vUsdAmount, SYSTEM_PRECISION).toFixed(), - }; + return this.service.getVUsdFromAmount(amount, amountFormat, sourceToken); } /** @@ -529,12 +370,7 @@ export class AllbridgeCoreSdk { * @return amount of destToken */ async getAmountFromVUsd(vUsdAmount: string, destToken: TokenWithChainDetails): Promise { - validateAmountGtZero(vUsdAmount); - const amount = swapFromVUsd(vUsdAmount, destToken, await getPoolInfoByToken(this.api, destToken)); - return { - [AmountFormat.INT]: amount, - [AmountFormat.FLOAT]: convertIntAmountToFloat(amount, destToken.decimals).toFixed(), - }; + return this.service.getAmountFromVUsd(vUsdAmount, destToken); } /** @@ -548,19 +384,7 @@ export class AllbridgeCoreSdk { sourceToken: TokenWithChainDetails, destToken: TokenWithChainDetails ): Promise { - return swapAndBridgeFeeCalculation( - amountInTokenPrecision, - { - decimals: sourceToken.decimals, - feeShare: sourceToken.feeShare, - poolInfo: await getPoolInfoByToken(this.api, sourceToken), - }, - { - decimals: destToken.decimals, - feeShare: destToken.feeShare, - poolInfo: await getPoolInfoByToken(this.api, destToken), - } - ); + return this.service.swapAndBridgeFeeCalculation(amountInTokenPrecision, sourceToken, destToken); } /** @@ -574,24 +398,7 @@ export class AllbridgeCoreSdk { sourceToken: TokenWithChainDetails, destToken: TokenWithChainDetails ): Promise { - const result = swapAndBridgeFeeCalculationReverse( - amountInTokenPrecision, - { - decimals: sourceToken.decimals, - feeShare: sourceToken.feeShare, - poolInfo: await getPoolInfoByToken(this.api, sourceToken), - }, - { - decimals: destToken.decimals, - feeShare: destToken.feeShare, - poolInfo: await getPoolInfoByToken(this.api, destToken), - } - ); - const newAmount = result.swapFromVUsdCalcResult.amountIncludingCommissionInTokenPrecision; - if (Big(newAmount).lt(0)) { - throw new InsufficientPoolLiquidityError(); - } - return result; + return this.service.swapAndBridgeFeeCalculationReverse(amountInTokenPrecision, sourceToken, destToken); } /** @@ -603,21 +410,6 @@ export class AllbridgeCoreSdk { sourceToken: TokenWithChainDetails, destToken: TokenWithChainDetails ): Promise { - validateAmountGtZero(amount); - let amountInTokenPrecision; - if (amountFormat == AmountFormat.FLOAT) { - validateAmountDecimals("amount", amount, sourceToken.decimals); - amountInTokenPrecision = convertFloatAmountToInt(amount, sourceToken.decimals).toFixed(); - } else { - amountInTokenPrecision = amount; - } - - return getSendAmountDetails( - amountInTokenPrecision, - sourceToken, - await getPoolInfoByToken(this.api, sourceToken), - destToken, - await getPoolInfoByToken(this.api, destToken) - ); + return this.service.getSendAmountDetails(amount, amountFormat, sourceToken, destToken); } } diff --git a/src/models/index.ts b/src/models/index.ts index 72e4e556..64d9cbc8 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -6,6 +6,7 @@ export { GetTokenBalanceParams, SendParams, SwapParams, + GetNativeTokenBalanceParams, } from "../services/bridge/models/bridge.model"; export { BridgeService } from "../services/bridge/index"; export { LiquidityPoolService } from "../services/liquidity-pool/index"; @@ -167,3 +168,21 @@ export interface ExtraGasMaxLimitResponse { */ sourceNativeTokenPrice: string; } + +/** + * Provide pending status information + */ +export interface PendingStatusInfoResponse { + /** + * Number of pending transactions + */ + pendingTxs: number; + /** + * Total amount of pending transactions + */ + pendingAmount: AmountFormatted; + /** + * The amount of tokens can be received as a result of transfer considering pending transactions + */ + estimatedAmount: { min: AmountFormatted; max: AmountFormatted }; +} diff --git a/src/services/bridge/index.ts b/src/services/bridge/index.ts index 76ed23d5..6bf42563 100644 --- a/src/services/bridge/index.ts +++ b/src/services/bridge/index.ts @@ -1,15 +1,12 @@ // @ts-expect-error import tron import TronWeb from "tronweb"; import Web3 from "web3"; +import { NodeRpcUrlsConfig } from ".."; import { chainProperties, ChainSymbol, ChainType } from "../../chains"; import { AllbridgeCoreClient } from "../../client/core-api"; -import { - CCTPDoesNotSupportedError, - Messenger, - MethodNotSupportedError, - NodeRpcUrlsConfig, - TokenWithChainDetails, -} from "../../index"; +import { Messenger } from "../../client/core-api/core-api.model"; +import { CCTPDoesNotSupportedError, MethodNotSupportedError } from "../../exceptions"; +import { TokenWithChainDetails } from "../../tokens-info"; import { validateAmountDecimals, validateAmountGtZero } from "../../utils"; import { Provider, TransactionResponse } from "../models"; import { TokenService } from "../token"; diff --git a/src/services/bridge/raw-bridge-transaction-builder.ts b/src/services/bridge/raw-bridge-transaction-builder.ts index 0d8cace0..30640f44 100644 --- a/src/services/bridge/raw-bridge-transaction-builder.ts +++ b/src/services/bridge/raw-bridge-transaction-builder.ts @@ -1,5 +1,5 @@ +import { NodeRpcUrlsConfig } from ".."; import { AllbridgeCoreClient } from "../../client/core-api"; -import { NodeRpcUrlsConfig } from "../../index"; import { validateAmountDecimals, validateAmountGtZero } from "../../utils"; import { Provider, RawTransaction } from "../models"; import { TokenService } from "../token"; diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 00000000..97f035af --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,497 @@ +import { Big } from "big.js"; +import { ChainSymbol } from "../chains"; +import { AllbridgeCoreClientImpl } from "../client/core-api"; +import { ApiClientImpl } from "../client/core-api/api-client"; +import { ApiClientCaching } from "../client/core-api/api-client-caching"; +import { Messenger, PendingInfoDTO, TransferStatusResponse } from "../client/core-api/core-api.model"; +import { AllbridgeCoreClientPoolInfoCaching } from "../client/core-api/core-client-pool-info-caching"; +import { mainnet } from "../configs"; +import { AllbridgeCoreSdkOptions, NodeRpcUrls } from "../index"; +import { + AmountFormat, + AmountFormatted, + AmountsAndGasFeeOptions, + CCTPDoesNotSupportedError, + ExtraGasMaxLimitResponse, + GasFeeOptions, + GetTokenBalanceParams, + InsufficientPoolLiquidityError, + NodeRpcUrlNotInitializedError, + PendingStatusInfoResponse, + Provider, + SendAmountDetails, + SwapAndBridgeCalculationData, +} from "../models"; +import { ChainDetailsMap, PoolInfo, PoolKeyObject, TokenWithChainDetails } from "../tokens-info"; +import { getPoolInfoByToken, validateAmountDecimals, validateAmountGtZero } from "../utils"; +import { + aprInPercents, + convertAmountPrecision, + convertFloatAmountToInt, + convertIntAmountToFloat, + fromSystemPrecision, + getFeePercent, + getSwapFromVUsdPoolInfo, + swapFromVUsd, + swapFromVUsdReverse, + swapToVUsd, + swapToVUsdReverse, +} from "../utils/calculation"; +import { SYSTEM_PRECISION } from "../utils/calculation/constants"; +import { getSendAmountDetails } from "../utils/calculation/swap-and-bridge-details"; +import { + swapAndBridgeFeeCalculation, + swapAndBridgeFeeCalculationReverse, +} from "../utils/calculation/swap-and-bridge-fee-calc"; +import { BridgeService, DefaultBridgeService } from "./bridge"; +import { GetNativeTokenBalanceParams } from "./bridge/models"; +import { SolanaBridgeParams } from "./bridge/sol"; +import { getExtraGasMaxLimits, getGasFeeOptions } from "./bridge/utils"; +import { DefaultLiquidityPoolService, LiquidityPoolService } from "./liquidity-pool"; +import { DefaultTokenService, TokenService } from "./token"; + +export class NodeRpcUrlsConfig { + constructor(private nodeRpcUrls: NodeRpcUrls) {} + + getNodeRpcUrl(chainSymbol: ChainSymbol): string { + const nodeRpcUrl = this.nodeRpcUrls[chainSymbol]; + if (nodeRpcUrl !== undefined) { + return nodeRpcUrl; + } else { + throw new NodeRpcUrlNotInitializedError(chainSymbol); + } + } +} + +export class AllbridgeCoreSdkService { + private readonly api: AllbridgeCoreClientPoolInfoCaching; + + private readonly tokenService: TokenService; + + readonly params: AllbridgeCoreSdkOptions; + + bridge: BridgeService; + pool: LiquidityPoolService; + + constructor(nodeRpcUrlsConfig: NodeRpcUrlsConfig, params: AllbridgeCoreSdkOptions = mainnet) { + const apiClient = new ApiClientImpl(params); + const apiClientTokenInfoCaching = new ApiClientCaching(apiClient); + const coreClient = new AllbridgeCoreClientImpl(apiClientTokenInfoCaching); + this.api = new AllbridgeCoreClientPoolInfoCaching(coreClient); + + const solBridgeParams: SolanaBridgeParams = { + wormholeMessengerProgramId: params.wormholeMessengerProgramId, + solanaLookUpTable: params.solanaLookUpTable, + }; + this.tokenService = new DefaultTokenService(this.api, nodeRpcUrlsConfig); + this.bridge = new DefaultBridgeService(this.api, nodeRpcUrlsConfig, solBridgeParams, this.tokenService); + this.pool = new DefaultLiquidityPoolService(this.api, nodeRpcUrlsConfig, this.tokenService); + this.params = params; + } + + async chainDetailsMap(): Promise { + return this.api.getChainDetailsMap(); + } + + async tokens(): Promise { + return this.api.tokens(); + } + + async tokensByChain(chainSymbol: ChainSymbol): Promise { + const map = await this.api.getChainDetailsMap(); + return map[chainSymbol].tokens; + } + + async getTransferStatus(chainSymbol: ChainSymbol, txId: string): Promise { + return this.api.getTransferStatus(chainSymbol, txId); + } + + async getPendingStatusInfo( + amount: string, + amountFormat: AmountFormat, + sourceToken: TokenWithChainDetails, + destToken: TokenWithChainDetails + ): Promise { + validateAmountGtZero(amount); + let amountInTokenPrecision; + if (amountFormat == AmountFormat.FLOAT) { + validateAmountDecimals("amount", amount, sourceToken.decimals); + amountInTokenPrecision = convertFloatAmountToInt(amount, sourceToken.decimals).toFixed(); + } else { + amountInTokenPrecision = amount; + } + + const vUsdAmountInt = swapToVUsd( + amountInTokenPrecision, + sourceToken, + await getPoolInfoByToken(this.api, sourceToken) + ); + const destPoolInfo = await getPoolInfoByToken(this.api, destToken); + const amountResultInt = swapFromVUsd(vUsdAmountInt, destToken, destPoolInfo); + if (Big(amountResultInt).lt(0)) { + throw new InsufficientPoolLiquidityError(); + } + + const result: PendingStatusInfoResponse = { + pendingTxs: 0, + pendingAmount: { + [AmountFormat.INT]: "0", + [AmountFormat.FLOAT]: "0", + }, + estimatedAmount: { + min: { + [AmountFormat.INT]: amountResultInt, + [AmountFormat.FLOAT]: convertIntAmountToFloat(amountResultInt, destToken.decimals).toFixed(), + }, + max: { + [AmountFormat.INT]: amountResultInt, + [AmountFormat.FLOAT]: convertIntAmountToFloat(amountResultInt, destToken.decimals).toFixed(), + }, + }, + }; + + let pendingInfoDTO: PendingInfoDTO | undefined; + const pendingInfo = await this.api.getPendingInfo(); + for (const tokenAddress in pendingInfo[destToken.chainSymbol]) { + if (tokenAddress.toLowerCase() === destToken.tokenAddress.toLowerCase()) { + pendingInfoDTO = pendingInfo[destToken.chainSymbol][tokenAddress]; + } + } + if (pendingInfoDTO) { + result.pendingTxs = pendingInfoDTO.pendingTxs; + result.pendingAmount[AmountFormat.INT] = convertAmountPrecision( + pendingInfoDTO.totalSentAmount, + SYSTEM_PRECISION, + destToken.decimals + ).toFixed(0); + result.pendingAmount[AmountFormat.FLOAT] = convertIntAmountToFloat( + pendingInfoDTO.totalSentAmount, + SYSTEM_PRECISION + ).toFixed(); + + const destPoolAfterPending = getSwapFromVUsdPoolInfo(pendingInfoDTO.totalSentAmount, destToken, destPoolInfo); + const amountResultIntAfterPending = swapFromVUsd(vUsdAmountInt, destToken, destPoolAfterPending); + if (Big(amountResultIntAfterPending).lt(0)) { + throw new InsufficientPoolLiquidityError(); + } + result.estimatedAmount.min = { + [AmountFormat.INT]: amountResultIntAfterPending, + [AmountFormat.FLOAT]: convertIntAmountToFloat(amountResultIntAfterPending, destToken.decimals).toFixed(), + }; + } + return result; + } + + async getTokenBalance(params: GetTokenBalanceParams, provider?: Provider): Promise { + return this.tokenService.getTokenBalance(params, provider); + } + + async getNativeTokenBalance(params: GetNativeTokenBalanceParams, provider?: Provider): Promise { + return this.tokenService.getNativeTokenBalance(params, provider); + } + + async calculateFeePercentOnSourceChain( + amountFloat: number | string | Big, + sourceChainToken: TokenWithChainDetails + ): Promise { + validateAmountGtZero(amountFloat); + validateAmountDecimals("amountFloat", amountFloat, sourceChainToken.decimals); + const amountInt = convertFloatAmountToInt(amountFloat, sourceChainToken.decimals); + if (amountInt.eq(0)) { + return 0; + } + const vUsdInSystemPrecision = swapToVUsd( + amountInt, + sourceChainToken, + await getPoolInfoByToken(this.api, sourceChainToken) + ); + const vUsdInSourcePrecision = fromSystemPrecision(vUsdInSystemPrecision, sourceChainToken.decimals); + return getFeePercent(amountInt, vUsdInSourcePrecision); + } + + async calculateFeePercentOnDestinationChain( + amountFloat: number | string | Big, + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails + ): Promise { + validateAmountGtZero(amountFloat); + validateAmountDecimals("amountFloat", amountFloat, sourceChainToken.decimals); + const amountInt = convertFloatAmountToInt(amountFloat, sourceChainToken.decimals); + if (amountInt.eq(0)) { + return 0; + } + const vUsdInSystemPrecision = swapToVUsd( + amountInt, + sourceChainToken, + await getPoolInfoByToken(this.api, sourceChainToken) + ); + const usd = swapFromVUsd( + vUsdInSystemPrecision, + destinationChainToken, + await getPoolInfoByToken(this.api, destinationChainToken) + ); + const vUsdInDestinationPrecision = fromSystemPrecision(vUsdInSystemPrecision, destinationChainToken.decimals); + return getFeePercent(vUsdInDestinationPrecision, usd); + } + + async getAmountToBeReceivedAndGasFeeOptions( + amountToSendFloat: number | string | Big, + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails, + messenger: Messenger + ): Promise { + validateAmountGtZero(amountToSendFloat); + validateAmountDecimals("amountToSendFloat", amountToSendFloat, sourceChainToken.decimals); + return { + amountToSendFloat: Big(amountToSendFloat).toFixed(), + amountToBeReceivedFloat: await this.getAmountToBeReceived( + amountToSendFloat, + sourceChainToken, + destinationChainToken, + messenger + ), + gasFeeOptions: await this.getGasFeeOptions(sourceChainToken, destinationChainToken, messenger), + }; + } + + async getAmountToSendAndGasFeeOptions( + amountToBeReceivedFloat: number | string | Big, + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails, + messenger: Messenger + ): Promise { + validateAmountGtZero(amountToBeReceivedFloat); + validateAmountDecimals("amountToBeReceivedFloat", amountToBeReceivedFloat, destinationChainToken.decimals); + return { + amountToSendFloat: await this.getAmountToSend( + amountToBeReceivedFloat, + sourceChainToken, + destinationChainToken, + messenger + ), + amountToBeReceivedFloat: Big(amountToBeReceivedFloat).toFixed(), + gasFeeOptions: await this.getGasFeeOptions(sourceChainToken, destinationChainToken, messenger), + }; + } + + async getAmountToBeReceived( + amountToSendFloat: number | string | Big, + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails, + messenger?: Messenger + ): Promise { + validateAmountGtZero(amountToSendFloat); + validateAmountDecimals("amountToSendFloat", amountToSendFloat, sourceChainToken.decimals); + const amountToSend = convertFloatAmountToInt(amountToSendFloat, sourceChainToken.decimals); + + if (messenger && messenger == Messenger.CCTP) { + if (!sourceChainToken.cctpAddress || !destinationChainToken.cctpAddress || !sourceChainToken.cctpFeeShare) { + throw new CCTPDoesNotSupportedError("Such route does not support CCTP protocol"); + } + const result = amountToSend.mul(Big(1).minus(sourceChainToken.cctpFeeShare)).round(0, Big.roundUp); + const resultInDestPrecision = convertAmountPrecision( + result, + sourceChainToken.decimals, + destinationChainToken.decimals + ).round(0); + return convertIntAmountToFloat(resultInDestPrecision, destinationChainToken.decimals).toFixed(); + } + + const vUsd = swapToVUsd(amountToSend, sourceChainToken, await getPoolInfoByToken(this.api, sourceChainToken)); + const resultInt = swapFromVUsd( + vUsd, + destinationChainToken, + await getPoolInfoByToken(this.api, destinationChainToken) + ); + if (Big(resultInt).lt(0)) { + throw new InsufficientPoolLiquidityError(); + } + return convertIntAmountToFloat(resultInt, destinationChainToken.decimals).toFixed(); + } + + async getAmountToSend( + amountToBeReceivedFloat: number | string | Big, + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails, + messenger?: Messenger + ): Promise { + validateAmountGtZero(amountToBeReceivedFloat); + validateAmountDecimals("amountToBeReceivedFloat", amountToBeReceivedFloat, destinationChainToken.decimals); + const amountToBeReceived = convertFloatAmountToInt(amountToBeReceivedFloat, destinationChainToken.decimals); + + if (messenger && messenger == Messenger.CCTP) { + if (!sourceChainToken.cctpAddress || !destinationChainToken.cctpAddress || !sourceChainToken.cctpFeeShare) { + throw new CCTPDoesNotSupportedError("Such route does not support CCTP protocol"); + } + const result = amountToBeReceived.div(Big(1).minus(sourceChainToken.cctpFeeShare)).round(0, Big.roundDown); + const resultInSourcePrecision = convertAmountPrecision( + result, + destinationChainToken.decimals, + sourceChainToken.decimals + ).round(0); + return convertIntAmountToFloat(resultInSourcePrecision, sourceChainToken.decimals).toFixed(); + } + + const vUsd = swapFromVUsdReverse( + amountToBeReceived, + destinationChainToken, + await getPoolInfoByToken(this.api, destinationChainToken) + ); + const resultInt = swapToVUsdReverse(vUsd, sourceChainToken, await getPoolInfoByToken(this.api, sourceChainToken)); + if (Big(resultInt).lte(0)) { + throw new InsufficientPoolLiquidityError(); + } + return convertIntAmountToFloat(resultInt, sourceChainToken.decimals).toFixed(); + } + + async getGasFeeOptions( + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails, + messenger: Messenger + ): Promise { + return getGasFeeOptions( + sourceChainToken.allbridgeChainId, + sourceChainToken.chainType, + destinationChainToken.allbridgeChainId, + sourceChainToken.decimals, + messenger, + this.api + ); + } + + getAverageTransferTime( + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails, + messenger: Messenger + ): number | null { + return ( + /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */ + sourceChainToken.transferTime?.[destinationChainToken.chainSymbol]?.[messenger] ?? null + ); + } + + async getPoolInfoByToken(token: TokenWithChainDetails): Promise { + return await this.api.getPoolInfoByKey({ chainSymbol: token.chainSymbol, poolAddress: token.poolAddress }); + } + + async refreshPoolInfo(tokens?: TokenWithChainDetails | TokenWithChainDetails[]): Promise { + if (tokens) { + const tokensArray = tokens instanceof Array ? tokens : [tokens]; + const poolKeys: PoolKeyObject[] = tokensArray.map((t) => { + return { chainSymbol: t.chainSymbol, poolAddress: t.poolAddress }; + }); + return this.api.refreshPoolInfo(poolKeys); + } + return this.api.refreshPoolInfo(); + } + + aprInPercents(apr: number): string { + return aprInPercents(apr); + } + + async getExtraGasMaxLimits( + sourceChainToken: TokenWithChainDetails, + destinationChainToken: TokenWithChainDetails + ): Promise { + return await getExtraGasMaxLimits(sourceChainToken, destinationChainToken, this.api); + } + + async getVUsdFromAmount( + amount: string, + amountFormat: AmountFormat, + sourceToken: TokenWithChainDetails + ): Promise { + validateAmountGtZero(amount); + let amountInTokenPrecision; + if (amountFormat == AmountFormat.FLOAT) { + validateAmountDecimals("amount", amount, sourceToken.decimals); + amountInTokenPrecision = convertFloatAmountToInt(amount, sourceToken.decimals).toFixed(); + } else { + amountInTokenPrecision = amount; + } + + const vUsdAmount = swapToVUsd(amountInTokenPrecision, sourceToken, await getPoolInfoByToken(this.api, sourceToken)); + return { + [AmountFormat.INT]: vUsdAmount, + [AmountFormat.FLOAT]: convertIntAmountToFloat(vUsdAmount, SYSTEM_PRECISION).toFixed(), + }; + } + + async getAmountFromVUsd(vUsdAmount: string, destToken: TokenWithChainDetails): Promise { + validateAmountGtZero(vUsdAmount); + const amount = swapFromVUsd(vUsdAmount, destToken, await getPoolInfoByToken(this.api, destToken)); + return { + [AmountFormat.INT]: amount, + [AmountFormat.FLOAT]: convertIntAmountToFloat(amount, destToken.decimals).toFixed(), + }; + } + + async swapAndBridgeFeeCalculation( + amountInTokenPrecision: string, + sourceToken: TokenWithChainDetails, + destToken: TokenWithChainDetails + ): Promise { + return swapAndBridgeFeeCalculation( + amountInTokenPrecision, + { + decimals: sourceToken.decimals, + feeShare: sourceToken.feeShare, + poolInfo: await getPoolInfoByToken(this.api, sourceToken), + }, + { + decimals: destToken.decimals, + feeShare: destToken.feeShare, + poolInfo: await getPoolInfoByToken(this.api, destToken), + } + ); + } + + async swapAndBridgeFeeCalculationReverse( + amountInTokenPrecision: string, + sourceToken: TokenWithChainDetails, + destToken: TokenWithChainDetails + ): Promise { + const result = swapAndBridgeFeeCalculationReverse( + amountInTokenPrecision, + { + decimals: sourceToken.decimals, + feeShare: sourceToken.feeShare, + poolInfo: await getPoolInfoByToken(this.api, sourceToken), + }, + { + decimals: destToken.decimals, + feeShare: destToken.feeShare, + poolInfo: await getPoolInfoByToken(this.api, destToken), + } + ); + const newAmount = result.swapFromVUsdCalcResult.amountIncludingCommissionInTokenPrecision; + if (Big(newAmount).lt(0)) { + throw new InsufficientPoolLiquidityError(); + } + return result; + } + + async getSendAmountDetails( + amount: string, + amountFormat: AmountFormat, + sourceToken: TokenWithChainDetails, + destToken: TokenWithChainDetails + ): Promise { + validateAmountGtZero(amount); + let amountInTokenPrecision; + if (amountFormat == AmountFormat.FLOAT) { + validateAmountDecimals("amount", amount, sourceToken.decimals); + amountInTokenPrecision = convertFloatAmountToInt(amount, sourceToken.decimals).toFixed(); + } else { + amountInTokenPrecision = amount; + } + + return getSendAmountDetails( + amountInTokenPrecision, + sourceToken, + await getPoolInfoByToken(this.api, sourceToken), + destToken, + await getPoolInfoByToken(this.api, destToken) + ); + } +} diff --git a/src/services/liquidity-pool/index.ts b/src/services/liquidity-pool/index.ts index 81eeff04..548ab696 100644 --- a/src/services/liquidity-pool/index.ts +++ b/src/services/liquidity-pool/index.ts @@ -2,9 +2,10 @@ import { Big } from "big.js"; // @ts-expect-error import tron import TronWeb from "tronweb"; import Web3 from "web3"; +import { NodeRpcUrlsConfig } from ".."; import { chainProperties, ChainSymbol, ChainType } from "../../chains"; import { AllbridgeCoreClient } from "../../client/core-api"; -import { MethodNotSupportedError, NodeRpcUrlsConfig } from "../../index"; +import { MethodNotSupportedError } from "../../exceptions"; import { PoolInfo, TokenWithChainDetails } from "../../tokens-info"; import { validateAmountDecimals, validateAmountGtZero } from "../../utils"; import { convertIntAmountToFloat, fromSystemPrecision } from "../../utils/calculation"; diff --git a/src/services/liquidity-pool/raw-pool-transaction-builder.ts b/src/services/liquidity-pool/raw-pool-transaction-builder.ts index f2087d24..a402215c 100644 --- a/src/services/liquidity-pool/raw-pool-transaction-builder.ts +++ b/src/services/liquidity-pool/raw-pool-transaction-builder.ts @@ -1,5 +1,5 @@ +import { NodeRpcUrlsConfig } from ".."; import { AllbridgeCoreClient } from "../../client/core-api"; -import { NodeRpcUrlsConfig } from "../../index"; import { validateAmountDecimals, validateAmountGtZero } from "../../utils"; import { convertFloatAmountToInt } from "../../utils/calculation"; import { SYSTEM_PRECISION } from "../../utils/calculation/constants"; diff --git a/src/services/token/index.ts b/src/services/token/index.ts index e1fe6001..9675387f 100644 --- a/src/services/token/index.ts +++ b/src/services/token/index.ts @@ -4,10 +4,11 @@ import TronWeb from "tronweb"; import Web3 from "web3"; import { ChainDecimalsByType, chainProperties, ChainSymbol, ChainType } from "../../chains"; import { AllbridgeCoreClient } from "../../client/core-api"; -import { AmountFormat, AmountFormatted, MethodNotSupportedError, NodeRpcUrlsConfig } from "../../index"; +import { AmountFormat, AmountFormatted, MethodNotSupportedError } from "../../models"; import { validateAmountDecimals, validateAmountGtZero } from "../../utils"; import { convertFloatAmountToInt, convertIntAmountToFloat } from "../../utils/calculation"; import { GetNativeTokenBalanceParams } from "../bridge/models"; +import { NodeRpcUrlsConfig } from "../index"; import { Provider, RawTransaction, TransactionResponse } from "../models"; import { EvmTokenService } from "./evm"; import { diff --git a/src/utils/calculation/index.ts b/src/utils/calculation/index.ts index de445b92..12ca38d7 100644 --- a/src/utils/calculation/index.ts +++ b/src/utils/calculation/index.ts @@ -76,6 +76,31 @@ export function swapFromVUsd( return Big(result).minus(fee).round(0, Big.roundDown).toFixed(); } +export function getSwapFromVUsdPoolInfo( + vUsdAmount: BigSource, + { feeShare, decimals }: Pick, + poolInfo: Omit +): Omit { + const amountValue = Big(vUsdAmount); + if (amountValue.lte(0)) { + return poolInfo; + } + const vUsdBalance = amountValue.plus(poolInfo.vUsdBalance); + const newAmount = getY(vUsdBalance, poolInfo.aValue, poolInfo.dValue); + let result = fromSystemPrecision(Big(poolInfo.tokenBalance).minus(newAmount), decimals); + const fee = Big(result).times(feeShare); + result = Big(result).minus(fee); + return { + aValue: poolInfo.aValue, + dValue: poolInfo.dValue, + tokenBalance: newAmount.toFixed(0), + vUsdBalance: vUsdBalance.toFixed(0), + totalLpAmount: Big(poolInfo.totalLpAmount).minus(result).round(0, Big.roundUp).toFixed(), + accRewardPerShareP: poolInfo.accRewardPerShareP, + p: poolInfo.p, + }; +} + /** * @param amount - vUsd amount should be received * @param feeShare diff --git a/src/utils/index.ts b/src/utils/index.ts index 6caa0dc4..c13f3695 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,12 +1,12 @@ import { Big, BigSource } from "big.js"; import { AllbridgeCoreClientPoolInfoCaching } from "../client/core-api/core-client-pool-info-caching"; import { ArgumentInvalidDecimalsError, InvalidAmountError, TimeoutError } from "../exceptions"; -import { TokenWithChainDetails } from "../tokens-info"; +import { PoolInfo, TokenWithChainDetails } from "../tokens-info"; export async function getPoolInfoByToken( api: AllbridgeCoreClientPoolInfoCaching, sourceChainToken: TokenWithChainDetails -) { +): Promise { return await api.getPoolInfoByKey({ chainSymbol: sourceChainToken.chainSymbol, poolAddress: sourceChainToken.poolAddress,