UNPKG

@nemoprotocol/vaults-sdk

Version:

A TypeScript SDK for interacting with Nemo Vaults on the Sui blockchain

1 lines 273 kB
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/modules/vaults.ts","../src/errors.ts","../src/utils/utils.ts","../src/utils/tickMath.ts","../src/utils/decimal.ts","../src/utils/math.ts","../src/constant/index.ts","../src/utils/est.ts","../src/utils/objects.ts","../src/utils/vault.ts","../src/utils/stake/haedal.ts","../src/utils/stake/volo.ts","../src/utils/stake/aftermath.ts","../src/utils/stake/stake.ts","../src/utils/cachedContent.ts","../src/utils/cache.ts","../src/modules/admin.ts"],"sourcesContent":["export * from './modules/vaults'\nexport * from './modules/admin'\nexport * from './types'","import { SuiClient } from \"@mysten/sui/client\";\nimport { TransactionObjectArgument } from \"@mysten/sui/transactions\";\nimport Decimal from \"decimal.js\";\nimport { MmtSDK } from \"@mmt-finance/clmm-sdk\";\nimport BN from \"bn.js\";\nimport { ExtendedPoolWithApr } from \"@mmt-finance/clmm-sdk/dist/types\";\n\nexport interface SdkConfig {\n suiClient: SuiClient\n mmtClmmSDK: MmtSDK\n}\n\nexport interface CalculateAmountParams {\n vault_id: string\n is_amount_a: boolean\n input_amount: string\n slippage: number\n side: InputType\n}\n\nexport interface CalculateAmountResult {\n request_id?: string\n side: InputType\n original_input_amount: string\n amount_a: string\n amount_b: string\n amount_limit_a: string\n amount_limit_b: string\n ft_amount: string\n is_amount_a: boolean\n swap_result?: SwapAmountResult\n partner?: string\n}\n\nexport type SwapAmountResult = {\n swap_in_amount: string\n swap_out_amount: string\n a2b: boolean\n is_exceed: boolean\n sui_stake_protocol: SuiStakeProtocol\n after_sqrt_price?: string\n route_obj?: any\n}\n\nexport enum SuiStakeProtocol {\n Mmt = 'Mmt',\n Haedal = 'Haedal',\n Volo = 'Volo',\n Aftermath = 'aftermath',\n}\n\nexport enum InputType {\n Both = 'both',\n OneSide = 'oneSide',\n}\n\nexport interface Vault {\n id: string\n clmm_pool_id: string\n free_balance_a: string\n free_balance_b: string\n fee_a: string\n fee_b: string\n seed_balance: string\n upper_price_scalling: string\n lower_price_scalling: string\n upper_price_scalling_dec: Decimal\n lower_price_scalling_dec: Decimal\n lower_trigger_price: string,\n upper_trigger_price: string,\n upper_trigger_price_scalling_dec: Decimal\n lower_trigger_price_scalling_dec: Decimal\n last_rebalance_sqrt_price: string\n last_rebalance_sqrt_price_dec: Decimal\n last_rebalance_time: string\n deposit_limit: string\n free_threshold_a: string\n free_threshold_b: string\n lock_threshold_a: string\n lock_threshold_b: string,\n\n slippage_up: string,\n slippage_down: string,\n slippage_up_dec: Decimal,\n slippage_down_dec: Decimal,\n // swap and reward fee percentage\n fee_val: string,\n // withdraw fee percentage\n withdraw_fee_val: string,\n // decimals of vault asset TokenA and TokenB\n decimals_a: string,\n decimals_b: string,\n coin_type_a: string\n coin_type_b: string\n lp_token_type: string\n config_type: string\n is_lock: boolean\n is_deposit_enabled: boolean\n pool_sqrt_price: string\n\n liquidity: string\n total_supply: string\n position: Position\n pool: ExtendedPoolWithApr\n}\n\nexport interface VaultWithInfo extends Vault {\n vault_info: UserVaultInfo\n}\n\nexport interface VaultBalance {\n vault_id: string,\n clmm_pool_id: string,\n owner: string,\n lp_token_type: string,\n lp_token_balance: string,\n lp_token_decimals: number,\n liquidity: string,\n tick_lower_index: number,\n tick_upper_index: number,\n amount_a: string,\n amount_b: string,\n coin_type_a: string,\n coin_type_b: string,\n decimal_a: string,\n decimal_b: string,\n}\n\nexport interface UserVaultInfo {\n vaultName: string;\n aliasName: string;\n vaultAddress: string;\n vaultId: string;\n poolId: string;\n sourceProtocol: string;\n sourceProtocolUrl: string;\n sourceProtocolLogoUrl: string;\n apy: string;\n apyRateChange: string;\n vaultType: string;\n vaultLogo: string;\n vaultPrice: string;\n vaultPriceRateChange: string;\n earningRateChange: string;\n decimal: string;\n leftCoinType: string;\n leftCoinName: string;\n leftCoinLogo: string;\n leftTokenAmount: string;\n leftCoinPrice: string;\n leftCoinDecimal: string;\n leftSwapUrl: string;\n rightCoinType: string;\n rightCoinName: string;\n rightCoinLogo: string;\n rightTokenAmount: string;\n rightCoinPrice: string;\n rightCoinDecimal: string;\n rightSwapUrl: string;\n vaultOverview: string;\n deploymentUnix: string;\n maturity: string;\n fee: string;\n cardShowTagList: string[];\n cumulativeEarning: string;\n lastHarvestUnix: string;\n tradeStatus: string;\n vaultContract: string;\n vaultContractVersionList: string[];\n vaultGroup: string;\n vaultBalanceCap: string;\n vaultSupply: string;\n points: string;\n boost: string;\n tvl: string;\n earnings: string;\n}\n\nexport interface UserVaultInfoResponse {\n count: number;\n data: UserVaultInfo[];\n msg: string;\n page: {\n pageIndex: number;\n pageSize: number;\n };\n}\n\nexport interface VaultHolder {\n address: string;\n usdValue: number;\n}\n\nexport interface VaultHoldersResponse {\n data: {\n [vaultId: string]: VaultHolder[];\n };\n msg: string;\n}\n\nexport interface YtHoldDetail {\n marketStateId: string;\n usdValue: number;\n ytAmount: number;\n}\n\nexport interface YtHoldResponse {\n data: {\n [vaultId: string]: YtHoldDetail;\n };\n msg: string;\n}\n\nexport interface Position {\n id: string\n pool_id: string\n name: string\n liquidity: string\n clmm_position_id: string\n clmm_pool_id: string\n tick_lower_index: number\n tick_upper_index: number\n type_x: string\n type_y: string\n price_lower_tick_dec: Decimal\n price_upper_tick_dec: Decimal\n}\n\nexport interface LiquidityInput {\n /**\n * The amount of coin A.\n */\n coin_amount_a: string\n\n /**\n * The amount of coin B.\n */\n coin_amount_b: string\n\n /**\n * The maximum amount of token A.\n */\n coin_amount_limit_a: string\n\n /**\n * The maximum amount of token B.\n */\n coin_amount_limit_b: string\n\n /**\n * The liquidity amount.\n */\n liquidity_amount: string\n\n /**\n * Whether to fix the amount of token A.\n */\n is_amount_a: boolean\n}\n\nexport type CoinAmounts = {\n coin_amount_a: string\n coin_amount_b: string\n}\n\nexport type DepositParams = {\n vault_id: string\n coin_object_a?: TransactionObjectArgument // If coin_object_a is provided, use coin_object_a. Please ensure coin_object is greater than or equal to amount_a\n coin_object_b?: TransactionObjectArgument // If coin_object_b is provided, use coin_object_b. Please ensure coin_object is greater than or equal to amount_b\n slippage: number\n deposit_result: CalculateAmountResult\n return_coin?: boolean\n}\n\nexport type WithdrawParams = {\n vault_id: string\n ft_amount: string\n slippage: number\n return_coin?: boolean\n}\n\nexport type CalculateRemoveAmountParams = {\n vault_id: string\n is_amount_a: boolean\n is_ft_input: boolean\n input_amount: string\n max_ft_amount: string\n slippage: number\n side: InputType\n request_id?: string\n}\n\nexport type CalculateRemoveAmountResult = {\n request_id?: string\n side: InputType\n amount_a: string\n amount_b: string\n amount_limit_a: string\n amount_limit_b: string\n burn_ft_amount: string\n swap_result?: SwapAmountResult\n}\n\n/**\n * Represents a paginated data page with optional cursor and limit.\n */\nexport type DataPage<T> = {\n data: T[]\n next_cursor?: any\n has_next_page: boolean\n}\n\n/**\n * Represents query parameters for pagination.\n */\nexport type PageQuery = {\n cursor?: any\n limit?: number | null\n}\n\n/**\n * Represents arguments for pagination, with options for fetching all data or using PageQuery.\n */\nexport type PaginationArgs = 'all' | PageQuery\n\n/**\n * Represents a SUI struct tag.\n */\nexport type SuiStructTag = {\n /**\n * The full address of the struct.\n */\n full_address: string\n\n /**\n * The source address of the struct.\n */\n source_address: string\n\n /**\n * The address of the struct.\n */\n address: string\n\n /**\n * The module to which the struct belongs.\n */\n module: string\n\n /**\n * The name of the struct.\n */\n name: string\n\n /**\n * An array of type arguments (SUI addresses) for the struct.\n */\n type_arguments: string[]\n}\n\n/**\n * Represents a coin asset with address, object ID, and balance information.\n */\nexport type CoinAsset = {\n /**\n * The address type of the coin asset.\n */\n coin_type: string\n\n /**\n * The object identifier of the coin asset.\n */\n coin_object_id: string\n\n /**\n * The balance amount of the coin asset.\n */\n balance: bigint\n}\n\nexport type BuildCoinResult = {\n target_coin: TransactionObjectArgument\n selected_coins: string[]\n remain_coins: CoinAsset[]\n is_mint_zero_coin: boolean\n target_coin_amount: string\n original_spited_coin?: TransactionObjectArgument\n}\n\nexport interface PreSwapLpChangeParams {\n poolID: string;\n ticklower: number;\n tickUpper: number;\n deltaLiquidity: number;\n}\n\nexport interface FindRouterParams {\n from: string;\n target: string;\n amount: BN;\n byAmountIn: boolean;\n depth?: number;\n splitAlgorithm?: string;\n splitFactor?: number;\n splitCount?: number;\n providers?: string[];\n liquidityChanges?: PreSwapLpChangeParams[];\n}\n\nexport type WithdrawBothParams = {\n vault_id: string\n ft_amount: string\n slippage: number\n return_coin?: boolean\n}\n\nexport type WithdrawOneSideParams = {\n vault_id: string\n is_amount_a: boolean\n is_ft_input: boolean\n input_amount: string\n max_ft_amount: string\n slippage: number\n partner?: string\n return_coin?: boolean\n primary_coin_inputs?: TransactionObjectArgument\n}\n\nexport interface CoinMetadata {\n decimals: number;\n}\n\nexport interface CoinBalance {\n totalBalance: string;\n};\n","import {\n CalculateAmountParams,\n CalculateAmountResult,\n CalculateRemoveAmountParams,\n CalculateRemoveAmountResult, CoinMetadata,\n DepositParams,\n InputType,\n PaginationArgs,\n SdkConfig,\n SuiStakeProtocol,\n UserVaultInfo,\n UserVaultInfoResponse,\n Vault,\n VaultBalance,\n VaultHoldersResponse,\n VaultWithInfo,\n WithdrawBothParams,\n WithdrawOneSideParams,\n YtHoldResponse\n} from \"../types\";\nimport { getFullnodeUrl, SuiClient } from \"@mysten/sui/client\";\nimport { handleMessageError, VaultsErrorCode } from \"../errors\";\nimport {\n buildCoinWithBalance,\n buildVaultBalance,\n calculateDepositRatio,\n getCoinAmountFromLiquidity,\n getShareLiquidityByAmount,\n} from \"../utils/utils\";\nimport BN from \"bn.js\";\nimport Decimal from \"../utils/decimal\";\nimport { d, getDefaultSqrtPriceLimit, TickMath } from \"../utils/tickMath\";\nimport { coinWithBalance, Transaction, TransactionObjectArgument } from \"@mysten/sui/transactions\";\nimport {\n CLOCK_ADDRESS, IS_MMT_ORACLE_PRICE_SUI_PAIR, MMT_CLMM_PACKAGE_ID,\n MMT_CLMM_VERSION_ID,\n MMT_ORACLE_ID,\n MMT_ORACLE_PACKAGE_ID, NEMO_API_URL,\n PRICE_ADAPTER_PACKAGE_ID, PYTH_ORACLE_MAP, PYTH_STATE_ID, REGISTRY_ID, SET_PRICE_CAP_REGISTRY,\n VAULT_PACKAGE_ID,\n VAULT_VERSION_ID,\n VAULTS_WITHDRAW_MODULE,\n VaultsDepositModule,\n} from \"../constant\";\nimport { batchGetObjects } from \"../utils/objects\";\nimport { MmtSDK } from \"@mmt-finance/clmm-sdk\";\nimport { bcs } from \"@mysten/sui/bcs\";\nimport { buildVault, buildVaultByPoolMap, getLpAmountByLiquidity } from \"../utils/vault\";\nimport { findSuiStakeProtocol, getExchangeRateForStake, requestStakeCoin } from \"../utils/stake/stake\";\nimport { estLiquidityAmountFromFtAmount, estLiquidityAndCoinAmountFromOneAmounts } from \"../utils/est\";\nimport { CACHE_TIME_15S, CACHE_TIME_5MIN } from \"../utils/cachedContent\";\nimport { CacheUtil } from \"../utils/cache\";\nimport { ExtendedPoolWithApr, TokenSchema } from \"@mmt-finance/clmm-sdk/dist/types\";\n\nexport class Vaults {\n protected _config: SdkConfig\n protected _cache: CacheUtil\n protected _default_sender_address: string\n\n constructor(config: SdkConfig) {\n this._config = config\n this._cache = new CacheUtil();\n this._default_sender_address = '0xf55cc609b13e87470d3da78d39ad6f84458a8059eb06aa66f94103d775e8a663'\n }\n\n getConfig(): SdkConfig {\n return this._config\n }\n\n static createSDK(options: { client?: any, fullNodeUrl?: string, headers?: HeadersInit }): Vaults {\n let suiClient;\n let mmtClmmSDK;\n\n if (options.client) {\n suiClient = options.client;\n mmtClmmSDK = MmtSDK.NEW({network: \"mainnet\", client: options.client})\n return new Vaults({ suiClient: suiClient, mmtClmmSDK: mmtClmmSDK });\n }\n if (!options.fullNodeUrl) {\n suiClient = new SuiClient({ url: getFullnodeUrl('mainnet') });\n mmtClmmSDK = MmtSDK.NEW({ network: \"mainnet\" });\n } else {\n suiClient = new SuiClient({ url: options.fullNodeUrl });\n mmtClmmSDK = MmtSDK.NEW({\n network: \"mainnet\", suiClientUrl: options.fullNodeUrl, customHeaders: options.headers });\n }\n return new Vaults({ suiClient: suiClient, mmtClmmSDK: mmtClmmSDK });\n }\n\n public async getOwnerVaultBalance(wallet_address: any, vault_id: string): Promise<VaultBalance | null> {\n //get vault token type\n const vault = await this.getVault(vault_id);\n if (vault === undefined) {\n return handleMessageError(VaultsErrorCode.ObjectNotFound, `vault not found, vault_id: ${vault_id}`);\n }\n const [lp_token_balance, coinMetadata] = await Promise.all([\n this._config.suiClient.getBalance({\n owner: wallet_address,\n coinType: vault.lp_token_type,\n }),\n this._config.suiClient.getCoinMetadata({\n coinType: vault.lp_token_type,\n })\n ])\n\n if (lp_token_balance.totalBalance === '0') {\n return null;\n }\n const clmm_pool = await this._config.mmtClmmSDK.Pool.getPool(vault.clmm_pool_id)\n const wrap_data = buildVaultBalance(wallet_address, vault, lp_token_balance, coinMetadata!, clmm_pool.currentSqrtPrice)\n\n if (wrap_data) {\n return wrap_data;\n }\n return null;\n }\n\n /**\n * Get position tokenA and tokenB amounts for a specific vault with price data\n * @param vault_id - The vault ID to get position amounts for\n * @returns Object containing tokenA and tokenB amounts with their decimals and price data\n */\n public async getPositionAssets(vault_id: string): Promise<{\n amount_a: string;\n amount_b: string;\n decimal_a: number;\n decimal_b: number;\n coin_type_a: string;\n coin_type_b: string;\n price_a?: number;\n price_b?: number;\n value_usd_a?: number;\n value_usd_b?: number;\n total_value_usd?: number;\n lp_usd_price?: number;\n } | null> {\n const vault = await this.getVault(vault_id);\n if (vault === undefined) {\n return handleMessageError(VaultsErrorCode.ObjectNotFound, `vault not found, vault_id: ${vault_id}`);\n }\n\n const clmm_pool = vault.pool;\n\n // Get position tick range\n const { tick_lower_index, tick_upper_index, type_x, type_y } = vault.position;\n\n // Use MMT SDK TickMath to get sqrt prices\n const lower_sqrt_price = TickMath.tickIndexToSqrtPriceX64(tick_lower_index);\n const upper_sqrt_price = TickMath.tickIndexToSqrtPriceX64(tick_upper_index);\n\n // Calculate token amounts from liquidity using MMT SDK utilities\n const amount_info = getCoinAmountFromLiquidity(\n new BN(vault.liquidity),\n new BN(clmm_pool.currentSqrtPrice),\n lower_sqrt_price,\n upper_sqrt_price,\n true\n );\n\n // Fetch token prices using MMT SDK\n let price_a: number | undefined;\n let price_b: number | undefined;\n let value_usd_a: number | undefined;\n let value_usd_b: number | undefined;\n let total_value_usd: number | undefined;\n let lp_usd_price: number | undefined;\n\n try {\n // Use MMT SDK fetchTokenApi to get token price data\n const [tokenA, tokenB, coinMetadata] = await this.getTokenSchemas(vault)\n\n price_a = parseFloat(tokenA.price);\n price_b = parseFloat(tokenB.price);\n\n // Calculate USD values if prices are available\n if (price_a !== undefined && price_b !== undefined) {\n const decimal_a = parseInt(vault.decimals_a);\n const decimal_b = parseInt(vault.decimals_b);\n\n // Convert token amounts to decimal format and calculate USD values\n const amount_a_decimal = parseFloat(amount_info.coin_amount_a) / Math.pow(10, decimal_a);\n const amount_b_decimal = parseFloat(amount_info.coin_amount_b) / Math.pow(10, decimal_b);\n\n value_usd_a = amount_a_decimal * price_a;\n value_usd_b = amount_b_decimal * price_b;\n total_value_usd = value_usd_a + value_usd_b;\n\n // Calculate LP token USD value\n if (total_value_usd !== undefined && vault.total_supply !== '0') {\n const lp_decimals = coinMetadata?.decimals ?? 9;\n const total_supply_decimal = parseFloat(vault.total_supply) / Math.pow(10, lp_decimals);\n lp_usd_price = total_value_usd / total_supply_decimal;\n }\n }\n } catch (error) {\n console.error(`Failed to fetch token prices for vault ${vault_id}:`, error);\n throw error;\n }\n\n return {\n amount_a: amount_info.coin_amount_a.toString(),\n amount_b: amount_info.coin_amount_b.toString(),\n decimal_a: parseInt(vault.decimals_a),\n decimal_b: parseInt(vault.decimals_b),\n coin_type_a: type_x,\n coin_type_b: type_y,\n price_a,\n price_b,\n value_usd_a,\n value_usd_b,\n total_value_usd,\n lp_usd_price,\n };\n }\n\n private async getTokenSchemas(vault: Vault): Promise<[TokenSchema, TokenSchema, CoinMetadata | null]> {\n const tokenA = this.readTokenFromCache(vault.coin_type_a);\n const tokenB = this.readTokenFromCache(vault.coin_type_b);\n const coinMetadata = this.readCoinMetadataFromCache(vault.lp_token_type);\n if (tokenA !== undefined && tokenB !== undefined && coinMetadata !== undefined) {\n return [tokenA, tokenB, coinMetadata];\n } else {\n const [tokenA, tokenB, coinMetadata] = await Promise.all([\n this._config.mmtClmmSDK.Pool.getToken(vault.coin_type_a),\n this._config.mmtClmmSDK.Pool.getToken(vault.coin_type_b),\n this._config.suiClient.getCoinMetadata({\n coinType: vault.lp_token_type,\n })\n ]);\n this.saveTokenToCache(tokenA);\n this.saveTokenToCache(tokenB);\n if (coinMetadata) {\n this.saveCoinMetadataToCache(vault.lp_token_type, coinMetadata);\n }\n return [tokenA, tokenB, coinMetadata];\n }\n }\n\n /**\n * Get remaining deposit capacity for a specific vault\n * @param vault_id - The vault ID to get remaining capacity for\n * @returns Object containing deposit limit, current position value, and remaining capacity in USD\n */\n public async getRemainingCap(vault_id: string): Promise<{\n deposit_limit_usd: number;\n current_position_value_usd: number;\n remaining_cap_usd: number;\n } | null> {\n const vault = await this.getVault(vault_id);\n if (vault === undefined) {\n return handleMessageError(VaultsErrorCode.ObjectNotFound, `vault not found, vault_id: ${vault_id}`);\n }\n\n // Get position assets to get the current total USD value\n const positionAssets = await this.getPositionAssets(vault_id);\n if (!positionAssets) {\n return null;\n }\n\n // Convert deposit limit from raw amount to USD\n // deposit_limit is typically in the base token units, we need to get the current position value\n const deposit_limit_raw = parseFloat(vault.deposit_limit);\n\n // If deposit limit is 0, it means unlimited\n if (deposit_limit_raw === 0) {\n return {\n deposit_limit_usd: Number.MAX_SAFE_INTEGER,\n current_position_value_usd: positionAssets.total_value_usd || 0,\n remaining_cap_usd: Number.MAX_SAFE_INTEGER,\n };\n }\n\n // For now, we'll use the total position value as the basis\n // This assumes the deposit_limit is in USD terms or equivalent\n const current_position_value_usd = positionAssets.total_value_usd || 0;\n const deposit_limit_usd = deposit_limit_raw / Math.pow(10, 9); // Assuming deposit_limit is already in USD terms\n const remaining_cap_usd = Math.max(0, deposit_limit_usd - current_position_value_usd);\n\n return {\n deposit_limit_usd,\n current_position_value_usd,\n remaining_cap_usd,\n };\n }\n\n public async getOwnerVaultsBalance(wallet_address: any): Promise<VaultBalance[]> {\n const data = await this.getVaultList()\n const result = []\n for (let i = 0; i < data.length; i++) {\n const vault = data[i]\n const lp_token_balance = await this._config.suiClient.getBalance({\n owner: wallet_address,\n coinType: vault.lp_token_type,\n })\n if (lp_token_balance.totalBalance === '0') {\n continue;\n }\n const coinMetadata = await this._config.suiClient.getCoinMetadata({\n coinType: vault.lp_token_type,\n })\n const clmm_pool = await this._config.mmtClmmSDK.Pool.getPool(vault.clmm_pool_id)\n const wrap_data = buildVaultBalance(wallet_address, vault, lp_token_balance, coinMetadata!, clmm_pool.currentSqrtPrice)\n if (wrap_data) {\n result.push(wrap_data)\n }\n }\n return result\n }\n\n public async getOwnerVaultsBalanceObj(wallet_address: any): Promise<Record<string, VaultBalance>> {\n\n let warpIds\n let coinMetadataList: string[];\n try {\n const response = await fetch(`${NEMO_API_URL}/market/vaultIdList`);\n if (!response.ok) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault IDs: ${response.statusText}`);\n }\n const data = await response.json(); // 如果是 JSON 响应\n\n warpIds = data.data.map((item: { vaultId: string }) => item.vaultId);\n coinMetadataList = data.data.map((item: { vaultId: string, coinMetadataId: string }) => item.coinMetadataId);\n } catch (error) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault IDs: ${error}`);\n }\n\n const vaults = await this.getVaultByIds(warpIds)\n\n const vaultObj: Record<string, VaultBalance> = {};\n\n const coinBalances = await this._config.suiClient.getAllBalances({ owner: wallet_address });\n const coinBalanceMap = Object.fromEntries(\n coinBalances.map((coinBalance) => [coinBalance.coinType, coinBalance.totalBalance])\n );\n\n const coinMetadatas = await this._config.suiClient.multiGetObjects({\n ids: coinMetadataList,\n options: { showType: true, showContent: true, showDisplay: true, showOwner: true },\n });\n console.log('coinMetadatas', JSON.stringify(coinMetadatas));\n const coinMetadataMap: Record<string, number> = {};\n for (const coinMetadata of coinMetadatas) {\n const metadata = coinMetadata.data?.content as unknown as { fields: { decimals: number } };\n const type = coinMetadata.data?.type;\n if (metadata && type) {\n const match = type.match(/<([^>]+)>/);\n if (match) {\n coinMetadataMap[match[1]] = metadata.fields.decimals;\n }\n }\n }\n\n for (let i = 0; i < vaults.length; i++) {\n const vault = vaults[i]\n const totalBalance = coinBalanceMap[vault.lp_token_type];\n if (totalBalance === '0') {\n continue;\n }\n const decimals = coinMetadataMap[vault.lp_token_type];\n if (!decimals) {\n return handleMessageError(VaultsErrorCode.FetchError, `Coin metadata not found for type: ${vault.lp_token_type}`);\n }\n const wrap_data = buildVaultBalance(\n wallet_address, vault, { totalBalance: totalBalance }, { decimals: decimals }, vault.pool.currentSqrtPrice)\n\n if (wrap_data) {\n vaultObj[wrap_data.vault_id] = wrap_data;\n }\n }\n return vaultObj\n }\n\n /**\n * Get list of vault holders for a specific vault with their USD values\n * @param vault_id - The vault ID to get holders for\n * @returns Vault holders response with addresses and USD values\n */\n public async getVaultHolders(vault_id: string): Promise<VaultHoldersResponse> {\n try {\n const response = await fetch(`${NEMO_API_URL}/market/vault/holderList?vaultId=${vault_id}`);\n if (!response.ok) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault holders: ${response.statusText}`);\n }\n const data = await response.json();\n\n if (!data.data || typeof data.data !== 'object') {\n return handleMessageError(VaultsErrorCode.FetchError, 'Invalid response format for vault holders');\n }\n\n return data as VaultHoldersResponse;\n } catch (error) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault holders: ${error}`);\n }\n }\n\n /**\n * Get detailed vault information including APY, earnings, and token details\n * @param vault_id - The vault ID to get information for\n * @returns Vault information with earnings, APY, and token details\n */\n public async getVaultInfo(vault_id: string): Promise<UserVaultInfo> {\n try {\n const params = new URLSearchParams({\n vaultId: vault_id\n });\n\n const response = await fetch(`${NEMO_API_URL}/market/user/vaultInfo?${params}`);\n if (!response.ok) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault info: ${response.statusText}`);\n }\n\n const data: UserVaultInfoResponse = await response.json();\n\n if (!data.data || !Array.isArray(data.data)) {\n return handleMessageError(VaultsErrorCode.FetchError, 'Invalid response format for vault info');\n }\n\n return data.data[0];\n } catch (error) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault info: ${error}`);\n }\n }\n\n /**\n * Get YT (Yield Token) hold details for a specific address\n * @param address - The wallet address to get YT hold details for\n * @returns YT hold information including market state, volume, and YT amount\n */\n public async getYtHoldDetail(address: string): Promise<YtHoldResponse> {\n try {\n const params = new URLSearchParams({\n address: address\n });\n\n const response = await fetch(`${NEMO_API_URL}/ytHold/detail?${params}`);\n if (!response.ok) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch YT hold detail: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (!data.data || typeof data.data !== 'object') {\n return handleMessageError(VaultsErrorCode.FetchError, 'Invalid response format for YT hold detail');\n }\n\n return data as YtHoldResponse;\n } catch (error) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch YT hold detail: ${error}`);\n }\n }\n\n public async getOwnerVaultsBalanceByVaultId(wallet_address: any, vault_id: string): Promise<VaultBalance | undefined> {\n const vault = await this.getVault(vault_id);\n if (vault === undefined) {\n return handleMessageError(VaultsErrorCode.ObjectNotFound, `vault not found, vault_id: ${vault_id}`);\n }\n const lp_token_balance = await this._config.suiClient.getBalance({\n owner: wallet_address,\n coinType: vault.lp_token_type,\n })\n if (lp_token_balance.totalBalance === '0') {\n return undefined;\n }\n const clmm_pool = await this._config.mmtClmmSDK.Pool.getPool(vault.clmm_pool_id)\n const coinMetadata = await this._config.suiClient.getCoinMetadata({\n coinType: vault.lp_token_type,\n })\n const wrap_data = buildVaultBalance(wallet_address, vault, lp_token_balance, coinMetadata!, clmm_pool.currentSqrtPrice)\n return wrap_data;\n }\n\n async getAllVaultObj(pagination_args: PaginationArgs = 'all'): Promise<Record<string, Vault>> {\n const poolList = await this.getVaultList();\n const vaultObj: Record<string, Vault> = {};\n for (const vault of poolList) {\n vaultObj[vault.id] = vault;\n }\n return vaultObj\n }\n\n async getVaultList(pagination_args: PaginationArgs = 'all'): Promise<Vault[]> {\n let warpIds\n try {\n const response = await fetch(`${NEMO_API_URL}/market/vaultIdList`);\n if (!response.ok) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault IDs: ${response.statusText}`);\n }\n const data = await response.json(); // 如果是 JSON 响应\n\n warpIds = data.data.map((item: { vaultId: string }) => item.vaultId);\n } catch (error) {\n return handleMessageError(VaultsErrorCode.FetchError, `Failed to fetch vault IDs: ${error}`);\n }\n\n return await this.getVaultByIds(warpIds)\n }\n\n private async getVaultByIds(ids: string[]): Promise<Vault[]> {\n const objectList = await batchGetObjects(this._config.suiClient, ids, {\n showType: true,\n showContent: true,\n showDisplay: true,\n showOwner: true,\n })\n const extendedPoolWithAprs = await this._config.mmtClmmSDK.Pool.getAllPools();\n const poolMap = Object.fromEntries(\n extendedPoolWithAprs.map(item => [item.poolId, item])\n ) as Record<string, ExtendedPoolWithApr>;\n\n const vaults: Vault[] = []\n for (const item of objectList) {\n try {\n const vault = await buildVaultByPoolMap(poolMap, this._config.suiClient, item)\n if (vault) {\n vaults.push(vault)\n }\n } catch (error) {\n return handleMessageError(VaultsErrorCode.BuildError, `Failed to build vault: ${JSON.stringify(item)}, ${error}`)\n }\n }\n return vaults\n }\n\n async getVault(id: string, force_refresh = false): Promise<VaultWithInfo | undefined> {\n const cache_pool = this.readVaultFromCache(id, force_refresh)\n if (cache_pool) {\n return cache_pool\n }\n\n try {\n const item: any = await this._config.suiClient.getObject({\n id,\n options: { showType: true, showContent: true, showDisplay: true, showOwner: true },\n })\n\n const [vault, vault_info] = await Promise.all([buildVault(this._config.mmtClmmSDK, this._config.suiClient, item), this.getVaultInfo(id)])\n if (vault) {\n const vaultWithInfo: VaultWithInfo = { ...vault, vault_info }\n this.saveVaultToCache(vaultWithInfo)\n return vaultWithInfo\n } else {\n return handleMessageError(VaultsErrorCode.ObjectNotFound, `vault not found, vault info:${item}`);\n }\n } catch (error) {\n return handleMessageError(VaultsErrorCode.FetchError, String(error));\n }\n }\n\n private saveVaultToCache(vault: Vault) {\n const cacheKey = `${vault.id}_mirror_vault`\n this._cache.updateCache(cacheKey, vault, CACHE_TIME_5MIN)\n }\n\n private readVaultFromCache(id: string, force_refresh = false) {\n const cache_key = `${id}_mirror_vault`\n return this._cache.getCache<VaultWithInfo>(cache_key, force_refresh)\n }\n\n private saveTokenToCache(token: TokenSchema) {\n const cacheKey = `${token.coinType}_mirror_token`\n this._cache.updateCache(cacheKey, token, CACHE_TIME_15S)\n }\n\n private readTokenFromCache(coinType: string, force_refresh = false) {\n const cache_key = `${coinType}_mirror_token`\n return this._cache.getCache<TokenSchema>(cache_key, force_refresh)\n }\n\n private saveCoinMetadataToCache(tokenType: string, coinMetadata: CoinMetadata) {\n const cacheKey = `${tokenType}_mirror_coin_metadata`\n this._cache.updateCache(cacheKey, coinMetadata, CACHE_TIME_15S)\n }\n\n private readCoinMetadataFromCache(tokenType: string, force_refresh = false) {\n const cache_key = `${tokenType}_mirror_coin_metadata`\n return this._cache.getCache<CoinMetadata>(cache_key, force_refresh)\n }\n\n private async getVaultAndPool(vaultId: string) {\n // Get vault information\n const vault = await this.getVault(vaultId)\n\n if (vault === undefined) {\n return handleMessageError(VaultsErrorCode.ObjectNotFound, 'vault not found')\n }\n return {\n vault,\n pool: vault.pool,\n }\n }\n\n public async calculateDepositAmount(\n params: CalculateAmountParams,\n should_request_stake = true,\n adjust_best_amount = false\n ): Promise<CalculateAmountResult> {\n if (params.side === InputType.Both) {\n return await this.calculateAmountFromBoth(params, true)\n }\n return await this.calculateDepositAmountFromOneSide(params, should_request_stake, adjust_best_amount)\n }\n\n private async calculateAmountFromBoth(params: CalculateAmountParams, round_up: boolean): Promise<CalculateAmountResult> {\n const { vault_id, input_amount, is_amount_a, slippage } = params\n // Get vault information\n const { vault, pool } = await this.getVaultAndPool(vault_id)\n\n // Extract position details\n // const {position} = vault\n const position = vault.position\n const lower_tick = position.tick_lower_index\n const upper_tick = position.tick_upper_index\n\n const liquidity_input = estLiquidityAndCoinAmountFromOneAmounts(\n lower_tick,\n upper_tick,\n new BN(input_amount),\n is_amount_a,\n round_up,\n slippage,\n new BN(pool.currentSqrtPrice)\n )\n\n const ft_amount = getLpAmountByLiquidity(vault, liquidity_input.liquidity_amount.toString())\n\n return {\n request_id: params.input_amount,\n side: InputType.Both,\n amount_a: liquidity_input.coin_amount_a.toString(),\n amount_b: liquidity_input.coin_amount_b.toString(),\n amount_limit_a: liquidity_input.coin_amount_limit_a.toString(),\n amount_limit_b: liquidity_input.coin_amount_limit_b.toString(),\n original_input_amount: params.input_amount,\n ft_amount,\n is_amount_a,\n }\n }\n\n private async calculateDepositAmountFromOneSide(\n params: CalculateAmountParams,\n should_request_stake: boolean,\n adjust_best_amount = false,\n use_route = true,\n max_loop_limit = 5,\n max_remain_rate = 0.02\n ): Promise<CalculateAmountResult> {\n try {\n const { vault_id, input_amount, is_amount_a: fix_input_amount_a, slippage } = params\n // Get vault information\n const { vault, pool } = await this.getVaultAndPool(vault_id)\n const { position } = vault\n const lowerTick = position.tick_lower_index\n const upperTick = position.tick_upper_index\n\n const { ratio_a, ratio_b } = calculateDepositRatio(lowerTick, upperTick, new BN(pool.currentSqrtPrice))\n\n const fix_amount = d(input_amount).mul(fix_input_amount_a ? ratio_a : ratio_b)\n const swap_amount = d(input_amount).sub(fix_amount)\n const a2b = fix_input_amount_a\n if (swap_amount.toFixed(0) === '0') {\n return await this.calculateAmountFromBoth(params, true)\n }\n\n let is_amount_a\n let swap_data\n let pares_swap_data\n let after_sqrt_price\n let swap_in_amount\n let swap_out_amount\n let swap_out_amount_limit\n const suiStakeProtocol = findSuiStakeProtocol(vault.coin_type_a, vault.coin_type_b, fix_input_amount_a)\n if (suiStakeProtocol !== SuiStakeProtocol.Mmt) {\n swap_data = await this.calculateStakeDepositFixSui({\n input_sui_amount: d(params.input_amount),\n swap_sui_amount: swap_amount,\n lower_tick: lowerTick,\n upper_tick: upperTick,\n cur_sqrt_price: pool.currentSqrtPrice.toString(),\n remain_rate: 0.01,\n fix_coin_a: params.is_amount_a,\n rebalance_count: 0,\n should_request_stake: should_request_stake,\n left_sui_amount: a2b ? new Decimal(swap_amount.toFixed(0)) : new Decimal(0),\n right_sui_amount: a2b ? new Decimal(params.input_amount) : new Decimal(swap_amount.toFixed(0)),\n slippage,\n stake_protocol: suiStakeProtocol,\n })\n after_sqrt_price = pool.currentSqrtPrice.toString()\n is_amount_a = swap_data.fix_amount_a\n swap_in_amount = swap_data.swap_in_amount\n swap_out_amount = swap_data.swap_out_amount\n swap_out_amount_limit = swap_data.swap_out_amount_limit\n } else {\n const tx = new Transaction();\n tx.moveCall({\n target: `${VAULT_PACKAGE_ID}::vault::get_optimal_swap_amount_for_single_sided_liquidity`,\n arguments: [\n tx.object(vault_id),\n tx.object(vault.clmm_pool_id),\n tx.pure.u64(input_amount),\n tx.pure.bool(params.is_amount_a),\n tx.pure.u64(20),\n ],\n typeArguments: [vault.coin_type_a, vault.coin_type_b, vault.lp_token_type, vault.config_type],\n })\n let result = await this._config.suiClient.devInspectTransactionBlock({\n sender: this._default_sender_address,\n transactionBlock: await tx.build({\n client: this._config.suiClient,\n onlyTransactionKind: true,\n }),\n });\n if (!result?.results?.[0]?.returnValues?.[0]) {\n return handleMessageError(VaultsErrorCode.FetchError, \"Failed to get swap amount\")\n }\n let out_amount = Decimal(bcs.U64.parse(new Uint8Array(result.results[0].returnValues[0][0])));\n console.log(\"get_optimal_swap_amount_for_single_sided_liquidity out_amount:\", input_amount, params.is_amount_a, out_amount.toString())\n swap_out_amount_limit = out_amount\n is_amount_a = params.is_amount_a\n swap_in_amount = out_amount\n // swap_out_amount = out_amount\n after_sqrt_price = pool.currentSqrtPrice.toString()\n }\n\n const coin_amount = is_amount_a === fix_input_amount_a ? d(input_amount).sub(swap_in_amount).toFixed(0) : swap_out_amount_limit\n\n const liquidity_input = estLiquidityAndCoinAmountFromOneAmounts(\n lowerTick,\n upperTick,\n new BN(coin_amount),\n is_amount_a,\n true,\n slippage,\n new BN(after_sqrt_price)\n )\n\n const amount_a = liquidity_input.coin_amount_a.toString()\n const amount_b = liquidity_input.coin_amount_b.toString()\n\n const lp_amount = getLpAmountByLiquidity(vault, liquidity_input.liquidity_amount.toString())\n return {\n request_id: params.input_amount,\n side: InputType.OneSide,\n amount_a,\n amount_b,\n amount_limit_a: liquidity_input.coin_amount_limit_a.toString(),\n amount_limit_b: liquidity_input.coin_amount_limit_b.toString(),\n ft_amount: lp_amount,\n original_input_amount: params.input_amount,\n is_amount_a: is_amount_a,\n swap_result: {\n swap_in_amount: swap_in_amount.toString(),\n swap_out_amount: amount_b.toString(),\n a2b: fix_input_amount_a,\n sui_stake_protocol: suiStakeProtocol,\n // route_obj: swap_data.route_obj,\n is_exceed: true,\n after_sqrt_price: after_sqrt_price,\n },\n }\n } catch (error) {\n if (use_route && (String(error) === 'Error: route unavailable' || String(error) === 'Error: router timeout')) {\n return await this.calculateDepositAmountFromOneSide(params, should_request_stake, false)\n }\n throw error\n }\n }\n\n /**\n * @param params\n */\n async calculateStakeDepositFixSui(params: {\n input_sui_amount: Decimal\n swap_sui_amount: Decimal\n left_sui_amount: Decimal\n right_sui_amount: Decimal\n lower_tick: number\n upper_tick: number\n cur_sqrt_price: string\n remain_rate: number\n fix_coin_a: boolean\n rebalance_count: number\n should_request_stake: boolean\n stake_protocol: SuiStakeProtocol\n slippage: number\n exchange_rate?: string\n }): Promise<any | null> {\n const remain_sui_limit = params.input_sui_amount.mul(params.remain_rate)\n const remain_sui = params.input_sui_amount.sub(params.swap_sui_amount)\n let exchange_rate\n if (params.should_request_stake) {\n exchange_rate = await getExchangeRateForStake(\n this,\n params.stake_protocol,\n this._default_sender_address,\n params.should_request_stake,\n Number(params.swap_sui_amount.toFixed(0))\n )\n } else {\n exchange_rate = params.exchange_rate\n ? params.exchange_rate\n : await getExchangeRateForStake(\n this,\n params.stake_protocol,\n this._default_sender_address,\n params.should_request_stake,\n Number(params.swap_sui_amount.toFixed(0))\n )\n }\n const hasui_amount = params.swap_sui_amount.div(exchange_rate).toFixed(0, Decimal.ROUND_DOWN)\n\n const liquidity_input = estLiquidityAndCoinAmountFromOneAmounts(\n params.lower_tick,\n params.upper_tick,\n new BN(hasui_amount.toString()),\n !params.fix_coin_a,\n true,\n 0,\n new BN(params.cur_sqrt_price)\n )\n const use_sui_amount = params.fix_coin_a ? liquidity_input.coin_amount_a.toString() : liquidity_input.coin_amount_b.toString()\n const act_remain_sui = d(remain_sui).sub(use_sui_amount)\n\n if (\n (act_remain_sui.greaterThanOrEqualTo(0) && act_remain_sui.lessThanOrEqualTo(remain_sui_limit)) ||\n params.rebalance_count > 12 ||\n params.left_sui_amount.greaterThanOrEqualTo(params.right_sui_amount)\n ) {\n return {\n swap_in_amount: params.swap_sui_amount.toFixed(0),\n swap_out_amount: hasui_amount,\n swap_out_amount_limit: d(hasui_amount)\n .mul(1 - params.slippage)\n .toFixed(0),\n after_sqrt_price: params.cur_sqrt_price,\n fix_amount_a: !params.fix_coin_a,\n is_exceed: true,\n request_id: '',\n stake_protocol: params.stake_protocol,\n }\n }\n if (act_remain_sui.lessThan(0)) {\n return await this.calculateStakeDepositFixSui({\n ...params,\n right_sui_amount: params.swap_sui_amount,\n swap_sui_amount: params.swap_sui_amount.add(params.left_sui_amount).div(2),\n exchange_rate,\n rebalance_count: params.rebalance_count + 1,\n })\n }\n\n if (act_remain_sui.greaterThan(remain_sui_limit)) {\n return await this.calculateStakeDepositFixSui({\n ...params,\n left_sui_amount: params.swap_sui_amount,\n swap_sui_amount: params.swap_sui_amount.add(params.right_sui_amount).div(2),\n exchange_rate,\n rebalance_count: params.rebalance_count + 1,\n })\n }\n\n return null\n }\n\n\n async deposit(params: DepositParams, sender_address: string, tx: Transaction): Promise<TransactionObjectArgument | undefined> {\n const { vault_id, slippage, coin_object_a, coin_object_b, return_coin, deposit_result } = params\n const { swap_result, amount_a, amount_b, is_amount_a, partner, side, original_input_amount } = deposit_result\n const { vault, pool } = await this.getVaultAndPool(vault_id)\n\n let primaryCoinAInputs\n let primaryCoinBInputs\n let in_coin\n if (side === InputType.OneSide && swap_result) {\n in_coin = (swap_result.a2b ? coin_object_a : coin_object_b) ||\n tx.add(coinWithBalance({\n balance: BigInt(original_input_amount),\n type: swap_result.a2b ? pool.tokenXType : pool.tokenYType\n }))\n\n const spitAmounts = [\n swap_result.swap_in_amount,\n d(original_input_amount).sub(d(swap_result.swap_in_amount)).toFixed(0, Decimal.ROUND_DOWN),\n ]\n const [swap_in_coin, amount_coin] = tx.splitCoins(in_coin, spitAmounts)\n\n console.log('spitCoins spitAmounts:', spitAmounts)\n\n const { swap_out_coin } = await this.handleDepositSwap(\n {\n coin_type_a: pool.tokenXType,\n coin_type_b: pool.tokenYType,\n slippage,\n clmm_pool_address: pool.poolId,\n partner,\n swap_in_amount: swap_result.swap_in_amount,\n swap_in_coin,\n a2b: swap_result.a2b,\n sui_stake_protocol: swap_result.sui_stake_protocol,\n route_obj: swap_result.route_obj,\n },\n sender_address,\n tx\n )\n if (swap_result.a2b) {\n primaryCoinAInputs = amount_coin\n primaryCoinBInputs = swap_out_coin\n } else {\n primaryCoinAInputs = swap_out_coin\n primaryCoinBInputs = amount_coin\n }\n }\n\n let amount_a_limit = d(amount_a).mul(d(1).add(slippage)).toFixed(0, Decimal.ROUND_DOWN).toString()\n let amount_b_limit = d(amount_b).mul(d(1).add(slippage)).toFixed(0, Decimal.ROUND_DOWN).toString()\n let fix_amount = is_amount_a ? amount_a : amount_b\n\n if (side === InputType.OneSide && swap_result) {\n amount_a_limit = '18446744073709551615'\n amount_b_limit = '18446744073709551615'\n if (swap_result.a2b) {\n fix_amount = d(fix_amount).mul(d(1).sub(0.001)).toFixed(0, Decimal.ROUND_DOWN).toString()\n primaryCoinAInputs = primaryCoinAInputs || buildCoinWithBalance(\n BigInt(is_amount_a ? amount_a : deposit_result.amount_limit_a), pool.tokenXType, tx)\n } else {\n fix_amount = d(fix_amount).mul(d(1).sub(0.001)).toFixed(0, Decimal.ROUND_DOWN).toString()\n primaryCoinBInputs = primaryCoinBInputs || buildCoinWithBalance(\n BigInt(is_amount_a ? deposit_result.amount_limit_b : deposit_result.amount_b), pool.tokenYType, tx)\n }\n } else {\n primaryCoinAInputs =\n coin_object_a || buildCoinWithBalance(BigInt(is_amount_a ? amount_a : amount_a_limit), pool.tokenXType, tx)\n primaryCoinBInputs =\n coin_object_b || buildCoinWithBalance(BigInt(is_amount_a ? amount_b_limit : amount_b), pool.tokenYType, tx)\n }\n\n if (primaryCoinAInputs === undefined || primaryCoinBInputs === undefined) {\n return handleMessageError(VaultsErrorCode.InsufficientCoins, \"Insufficient coins for deposit\")\n }\n\n const lpCoin = await this.depositInternal(\n {\n coin_type_a: pool.tokenXType,\n coin_type_b: pool.tokenYType,\n lp_token_type: vault.lp_token_type,\n config_type: vault.config_type,\n clmm_pool_id: pool.poolId,\n primary_coin_a_inputs: primaryCoinAInputs,\n primary_coin_b_inputs: primaryCoinBInputs,\n vault_id,\n slippage: params.slippage,\n amount_a: is_amount_a ? fix_amount : amount_a_limit,\n amount_b: is_amount_a ? amount_b_limit : fix_amount,\n is_amount_a: is_amount_a,\n return_coin,\n },\n tx\n )\n\n if (swap_result && in_coin) {\n tx.transferObjects([in_coin], tx.pure.address(sender_address))\n }\n\n if (return_coin) {\n return lpCoin\n }\n\n return undefined\n }\n\n private async depositInternal(\n params: {\n vault_id: string\n coin_type_a: string\n coin_type_b: string\n amount_a: string\n amount_b: string\n slippage: number\n is_amount_a: boolean\n lp_token_type: string\n config_type: string\n clmm_pool_id: string\n primary_coin_a_inputs: TransactionObjectArgument\n primary_coin_b_inputs: TransactionObjectArgument\n return_coin?: boolean\n },\n tx: Transaction\n ) {\n let { primary_coin_a_inputs, primary_coin_b_inputs } = params\n\n // if (primary_coin_a_inputs === undefined || primary_coin_b_inputs === undefined) {\n // const all_coin_asset = await getOwnerCoinAssets(this, this.getSenderAddress())\n // primary_coin_a_inputs = PositionUtils.buildAddLiquidityFixTokenCoinInput(\n // tx,\n // !params.fix_amount_a,\n // params.amount_a,\n // params.slippage,\n // params.coin_type_a,\n // all_coin_asset,\n // false\n // )?.target_coin\n //\n // primary_coin_b_inputs = PositionUtils.buildAddLiquidityFixTokenCoinInput(\n // tx,\n // params.fix_amount_a,\n // params.amount_b,\n // params.slippage,\n // params.coin_type_b,\n // all_coin_asset,\n // false\n // )?.target_coin\n // }\n\n const priceReceiptA = tx.moveCall({\n target: `${MMT_ORACLE_PACKAGE_ID}::oracle::get_price_receipt`,\n typeArguments: [params.coin_type_a],\n arguments: [tx.object(MMT_ORACLE_ID)],\n })\n if (!IS_MMT_ORACLE_PRICE_SUI_PAIR.includes(params.coin_type_a)) {\n tx.moveCall({\n target: `${PRICE_ADAPTER_PACKAGE_ID}::price_source::set_k_oracle_price`,\n typeArguments: [params.coin_type_a],\n arguments: [\n priceReceiptA,\n tx.object(MMT_ORACLE_ID),\n tx.object(REGISTRY_ID),\n tx.pure.bool(true),\n tx.object(PYTH_STATE_ID),\n tx.object(PYTH_ORACLE_MAP[params.coin_type_a].priceInfoObjectId),\n tx.object(SET_PRICE_CAP_REGISTRY),\n tx.object(CLOCK_ADDRESS),\n ],\n });\n } else {\n tx.moveCall({\n target: `${PRICE_ADAPTER_PACKAGE_ID}::price_source::set_mmt_oracle`,\n typeArguments: [params.coin_type_a, params.coin_type_b],\n arguments: [\n priceReceiptA,\n tx.object(params.clmm_pool_id),\n tx.object(MMT_ORACLE_ID),\n tx.object(PYTH_STATE_ID),\n tx.object(REGISTRY_ID),\n tx.object(PYTH_ORACLE_MAP[params.coin_type_b].priceInfoObjectId),\n tx.object(SET_PRICE_CAP_REGISTRY),\n tx.object(CLOCK_ADDRESS),\n tx.pure.bool(true),\n ],\n })\n }\n tx.moveCall({\n target: `${MMT_ORACLE_PACKAGE_ID}::oracle::update_price`,\n typeA