diff --git a/packages/sdk-ts/src/client/wasm/neptune/helper.ts b/packages/sdk-ts/src/client/wasm/neptune/helper.ts new file mode 100644 index 000000000..37a6443c6 --- /dev/null +++ b/packages/sdk-ts/src/client/wasm/neptune/helper.ts @@ -0,0 +1,9 @@ +import { AssetInfo } from './types.js' + +export function getDenom(assetInfo: AssetInfo): string | undefined { + if ('native_token' in assetInfo) { + return assetInfo.native_token.denom + } + + return assetInfo.token.contract_addr +} diff --git a/packages/sdk-ts/src/client/wasm/neptune/index.ts b/packages/sdk-ts/src/client/wasm/neptune/index.ts index 0b5449982..9dd369b0c 100644 --- a/packages/sdk-ts/src/client/wasm/neptune/index.ts +++ b/packages/sdk-ts/src/client/wasm/neptune/index.ts @@ -1,6 +1,6 @@ -export * from './queries' -export * from './transformer' -export * from './types' -export * from './service' +export * from './types.js' +export * from './service.js' +export * from './transformer.js' +export * from './queries/index.js' export const NEPTUNE_PRICE_CONTRACT = 'inj1u6cclz0qh5tep9m2qayry9k97dm46pnlqf8nre' diff --git a/packages/sdk-ts/src/client/wasm/neptune/queries/QueryGetPrices.ts b/packages/sdk-ts/src/client/wasm/neptune/queries/QueryGetPrices.ts index bc12c1548..2c5382a09 100644 --- a/packages/sdk-ts/src/client/wasm/neptune/queries/QueryGetPrices.ts +++ b/packages/sdk-ts/src/client/wasm/neptune/queries/QueryGetPrices.ts @@ -1,6 +1,6 @@ -import { BaseWasmQuery } from '../../BaseWasmQuery' -import { toBase64 } from '../../../../utils' -import { AssetInfo } from '../types' +import { BaseWasmQuery } from '../../BaseWasmQuery.js' +import { toBase64 } from '../../../../utils/index.js' +import { AssetInfo } from '../types.js' export declare namespace QueryGetPrices { export interface Params { diff --git a/packages/sdk-ts/src/client/wasm/neptune/queries/QueryLendingRates.ts b/packages/sdk-ts/src/client/wasm/neptune/queries/QueryLendingRates.ts new file mode 100644 index 000000000..48b3c6144 --- /dev/null +++ b/packages/sdk-ts/src/client/wasm/neptune/queries/QueryLendingRates.ts @@ -0,0 +1,26 @@ +import { BaseWasmQuery } from '../../BaseWasmQuery.js'; +import { toBase64 } from '../../../../utils/index.js'; +import { AssetInfo } from '../types.js'; + +export declare namespace QueryGetAllLendingRates { + export interface Params { + limit?: number; + startAfter?: AssetInfo; + } +} + +export class QueryGetAllLendingRates extends BaseWasmQuery { + toPayload() { + const { limit, startAfter } = this.params; + + const query: any = { get_all_lending_rates: {} }; + if (limit !== undefined) { + query.get_all_lending_rates.limit = limit; + } + if (startAfter !== undefined) { + query.get_all_lending_rates.start_after = startAfter; + } + + return toBase64(query); + } +} diff --git a/packages/sdk-ts/src/client/wasm/neptune/queries/index.ts b/packages/sdk-ts/src/client/wasm/neptune/queries/index.ts index 5ce0e07f3..65c4f006c 100644 --- a/packages/sdk-ts/src/client/wasm/neptune/queries/index.ts +++ b/packages/sdk-ts/src/client/wasm/neptune/queries/index.ts @@ -1 +1,2 @@ -export { QueryGetPrices } from './QueryGetPrices' +export { QueryGetPrices } from './QueryGetPrices.js' +export { QueryGetAllLendingRates } from './QueryLendingRates.js' diff --git a/packages/sdk-ts/src/client/wasm/neptune/service.ts b/packages/sdk-ts/src/client/wasm/neptune/service.ts index d82ecedb3..a6461db40 100644 --- a/packages/sdk-ts/src/client/wasm/neptune/service.ts +++ b/packages/sdk-ts/src/client/wasm/neptune/service.ts @@ -1,22 +1,28 @@ import { Network, isMainnet, + NetworkEndpoints, getNetworkEndpoints, } from '@injectivelabs/networks' -import { NetworkEndpoints } from '@injectivelabs/networks' -import { AssetInfo, NEPTUNE_USDT_CW20_CONTRACT, AssetInfoWithPrice } from './types' -import { ChainGrpcWasmApi } from '../../chain' -import { QueryGetPrices } from './queries' -import { PriceQueryTransformer } from './transformer' -import ExecArgNeptuneDeposit from '../../../core/modules/wasm/exec-args/ExecArgNeptuneDeposit' -import ExecArgNeptuneWithdraw from '../../../core/modules/wasm/exec-args/ExecArgNeptuneWithdraw' - -import MsgExecuteContractCompat from '../../../core/modules/wasm/msgs/MsgExecuteContractCompat' - +import { getDenom } from './helper.js' +import { ChainGrpcWasmApi } from '../../chain/index.js' +import { QueryGetPrices, QueryGetAllLendingRates } from './queries/index.js' +import { NeptuneQueryTransformer } from './transformer.js' +import ExecArgNeptuneDeposit from '../../../core/modules/wasm/exec-args/ExecArgNeptuneDeposit.js' +import ExecArgNeptuneWithdraw from '../../../core/modules/wasm/exec-args/ExecArgNeptuneWithdraw.js' +import MsgExecuteContractCompat from '../../../core/modules/wasm/msgs/MsgExecuteContractCompat.js' import { GeneralException } from '@injectivelabs/exceptions' -import { NEPTUNE_PRICE_CONTRACT } from './index' +import { NEPTUNE_PRICE_CONTRACT } from './index.js' +import { + AssetInfo, + AssetInfoWithPrice, + NEPTUNE_USDT_CW20_CONTRACT, +} from './types.js' -const NEPTUNE_USDT_MARKET_CONTRACT = 'inj1nc7gjkf2mhp34a6gquhurg8qahnw5kxs5u3s4u' +const NEPTUNE_USDT_MARKET_CONTRACT = + 'inj1nc7gjkf2mhp34a6gquhurg8qahnw5kxs5u3s4u' +const NEPTUNE_USDT_INTEREST_CONTRACT = + 'inj1ftech0pdjrjawltgejlmpx57cyhsz6frdx2dhq' export class NeptuneService { private client: ChainGrpcWasmApi @@ -29,7 +35,7 @@ export class NeptuneService { */ constructor( network: Network = Network.MainnetSentry, - endpoints?: NetworkEndpoints + endpoints?: NetworkEndpoints, ) { if (!isMainnet(network)) { throw new GeneralException(new Error('Please switch to mainnet network')) @@ -51,10 +57,11 @@ export class NeptuneService { try { const response = await this.client.fetchSmartContractState( this.priceOracleContract, - queryGetPricesPayload + queryGetPricesPayload, ) - const prices = PriceQueryTransformer.contractPricesResponseToPrices(response) + const prices = + NeptuneQueryTransformer.contractPricesResponseToPrices(response) return prices } catch (error) { @@ -69,8 +76,11 @@ export class NeptuneService { * @param nativeAsset AssetInfo for the native token. * @returns Redemption ratio as a number. */ - async fetchRedemptionRatio({ cw20Asset, nativeAsset }: { - cw20Asset: AssetInfo, + async fetchRedemptionRatio({ + cw20Asset, + nativeAsset, + }: { + cw20Asset: AssetInfo nativeAsset: AssetInfo }): Promise { const prices = await this.fetchPrices([cw20Asset, nativeAsset]) @@ -79,7 +89,9 @@ export class NeptuneService { const [nativePrice] = prices.reverse() if (!cw20Price || !nativePrice) { - throw new GeneralException(new Error('Failed to compute redemption ratio')) + throw new GeneralException( + new Error('Failed to compute redemption ratio'), + ) } return Number(cw20Price.price) / Number(nativePrice.price) @@ -113,10 +125,15 @@ export class NeptuneService { * @param amount Amount to deposit as a string. * @returns MsgExecuteContractCompat message. */ - createDepositMsg({ denom, amount, sender, contractAddress = NEPTUNE_USDT_MARKET_CONTRACT }: { - denom: string, - amount: string, - sender: string, + createDepositMsg({ + denom, + amount, + sender, + contractAddress = NEPTUNE_USDT_MARKET_CONTRACT, + }: { + denom: string + amount: string + sender: string contractAddress?: string }): MsgExecuteContractCompat { return MsgExecuteContractCompat.fromJSON({ @@ -143,10 +160,10 @@ export class NeptuneService { cw20ContractAddress = NEPTUNE_USDT_CW20_CONTRACT, marketContractAddress = NEPTUNE_USDT_MARKET_CONTRACT, }: { - amount: string, - sender: string, - cw20ContractAddress?: string, - marketContractAddress?: string, + amount: string + sender: string + cw20ContractAddress?: string + marketContractAddress?: string }): MsgExecuteContractCompat { return MsgExecuteContractCompat.fromJSON({ sender, @@ -157,4 +174,95 @@ export class NeptuneService { }), }) } + + /** + * Fetch lending rates with optional pagination parameters. + * @param limit Maximum number of lending rates to fetch. + * @param startAfter AssetInfo to start after for pagination. + * @returns Array of [AssetInfo, Decimal256] tuples. + */ + async getLendingRates({ + limit, + startAfter, + contractAddress = NEPTUNE_USDT_INTEREST_CONTRACT, + }: { + limit?: number + startAfter?: AssetInfo + contractAddress?: string + }): Promise> { + const query = new QueryGetAllLendingRates({ limit, startAfter }) + const payload = query.toPayload() + + try { + const response = await this.client.fetchSmartContractState( + contractAddress, + payload, + ) + + const lendingRates = + NeptuneQueryTransformer.contractLendingRatesResponseToLendingRates( + response, + ) + + return lendingRates + } catch (error) { + console.error('Error fetching lending rates:', error) + throw new GeneralException(new Error('Failed to fetch lending rates')) + } + } + + /** + * Fetch the lending rate for a specific denom by querying the smart contract with pagination. + * @param denom The denomination string of the asset to find the lending rate for. + * @returns Lending rate as a string. + */ + async getLendingRateByDenom({ + denom, + contractAddress = NEPTUNE_USDT_INTEREST_CONTRACT, + }: { + denom: string + contractAddress?: string + }): Promise { + const limit = 10 + let startAfter = undefined + + while (true) { + const lendingRates = await this.getLendingRates({ + limit, + startAfter, + contractAddress, + }) + + if (lendingRates.length === 0) { + return + } + + for (const { assetInfo, lendingRate } of lendingRates) { + const currentDenom = getDenom(assetInfo) + + if (currentDenom === denom) { + return lendingRate + } + } + + if (lendingRates.length < limit) { + return + } + + const lastLendingRate = lendingRates[lendingRates.length - 1] + + startAfter = lastLendingRate.assetInfo + } + } + + /** + * Calculates APY from APR and compounding frequency. + * + * @param apr - The annual percentage rate as a decimal (e.g., 0.10 for 10%) + * @param compoundingFrequency - Number of times interest is compounded per year + * @returns The annual percentage yield as a decimal + */ + calculateAPY(apr: number, compoundingFrequency = 365): number { + return Math.pow(1 + apr / compoundingFrequency, compoundingFrequency) - 1 + } } diff --git a/packages/sdk-ts/src/client/wasm/neptune/transformer.ts b/packages/sdk-ts/src/client/wasm/neptune/transformer.ts index 5fe81126a..e889940db 100644 --- a/packages/sdk-ts/src/client/wasm/neptune/transformer.ts +++ b/packages/sdk-ts/src/client/wasm/neptune/transformer.ts @@ -1,8 +1,8 @@ -import { WasmContractQueryResponse } from '../types' -import { toUtf8 } from '../../../utils' -import { AssetInfo, PriceResponse } from './types' +import { WasmContractQueryResponse } from '../types.js' +import { toUtf8 } from '../../../utils/index.js' +import { AssetInfo, PriceResponse, LendingRateResponse } from './types.js' -export class PriceQueryTransformer { +export class NeptuneQueryTransformer { static contractPricesResponseToPrices( response: WasmContractQueryResponse, ): Array<{ assetInfo: AssetInfo; price: string }> { @@ -13,4 +13,15 @@ export class PriceQueryTransformer { price: priceInfo.price, })) } + + static contractLendingRatesResponseToLendingRates( + response: WasmContractQueryResponse, + ): Array<{ assetInfo: AssetInfo; lendingRate: string }> { + const data = JSON.parse(toUtf8(response.data)) as LendingRateResponse + + return data.map(([assetInfo, lendingRate]) => ({ + assetInfo, + lendingRate, + })) + } } diff --git a/packages/sdk-ts/src/client/wasm/neptune/types.ts b/packages/sdk-ts/src/client/wasm/neptune/types.ts index 9a4807bbc..19c15a78d 100644 --- a/packages/sdk-ts/src/client/wasm/neptune/types.ts +++ b/packages/sdk-ts/src/client/wasm/neptune/types.ts @@ -13,6 +13,7 @@ export type AssetInfo = export type AssetInfoWithPrice = {assetInfo: AssetInfo, price: string } export type PriceResponse = Array<[AssetInfo, { price: string }]> +export type LendingRateResponse = Array<[AssetInfo, string]> export const NEPTUNE_USDT_CW20_CONTRACT = 'inj1cy9hes20vww2yr6crvs75gxy5hpycya2hmjg9s'