Skip to content

Commit

Permalink
Add price methods to kamino-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
peroxy committed Oct 30, 2023
1 parent 4bb39ed commit 0c6049d
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 10 deletions.
123 changes: 113 additions & 10 deletions packages/kamino-sdk/src/Kamino.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ import {
WithdrawAllAndCloseIxns,
numberToReferencePriceType,
stripTwapZeros,
getTokenNameFromCollateralInfo,
} from './utils';
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import {
Expand Down Expand Up @@ -243,18 +244,17 @@ import {
} from './utils/transactions';
import { RouteInfo } from '@jup-ag/core';
import { SwapResponse } from '@jup-ag/api';
import { StrategyPrices } from './models/StrategyPrices';
import { getDefaultManualRebalanceFieldInfos, getManualRebalanceFieldInfos } from './rebalance_methods/manualRebalance';
import { StrategyPrices } from './models';
import { getDefaultManualRebalanceFieldInfos, getManualRebalanceFieldInfos } from './rebalance_methods';
import {
deserializePricePercentageRebalanceFromOnchainParams,
deserializePricePercentageRebalanceWithStateOverride,
getDefaultPricePercentageRebalanceFieldInfos,
getPositionRangeFromPercentageRebalanceParams,
getPricePercentageRebalanceFieldInfos,
readPricePercentageRebalanceParamsFromStrategy,
readPricePercentageRebalanceStateFromStrategy,
readRawPricePercentageRebalanceStateFromStrategy,
} from './rebalance_methods/pricePercentageRebalance';
} from './rebalance_methods';
import {
deserializePricePercentageWithResetRebalanceFromOnchainParams,
deserializePricePercentageWithResetRebalanceWithStateOverride,
Expand All @@ -263,7 +263,7 @@ import {
getPricePercentageWithResetRebalanceFieldInfos,
readPricePercentageWithResetRebalanceParamsFromStrategy,
readRawPricePercentageWithResetRebalanceStateFromStrategy,
} from './rebalance_methods/pricePercentageWithResetRebalance';
} from './rebalance_methods';
import {
deserializeDriftRebalanceFromOnchainParams,
deserializeDriftRebalanceWithStateOverride,
Expand All @@ -272,7 +272,7 @@ import {
getPositionRangeFromDriftParams,
readDriftRebalanceParamsFromStrategy,
readRawDriftRebalanceStateFromStrategy,
} from './rebalance_methods/driftRebalance';
} from './rebalance_methods';
import {
deserializeExpanderRebalanceWithStateOverride,
deserializePeriodicRebalanceFromOnchainParams,
Expand Down Expand Up @@ -311,6 +311,7 @@ import {
getDefaultAutodriftRebalanceFieldInfos,
getPositionRangeFromAutodriftParams,
} from './rebalance_methods/autodriftRebalance';
import { EnrichedScopePrice, KaminoPrices, OraclePricesAndCollateralInfos } from './models/EnrichedScopePrice';
export const KAMINO_IDL = KaminoIdl;

export class Kamino {
Expand All @@ -324,7 +325,6 @@ export class Kamino {
private readonly _kaminoProgramId: PublicKey;
private readonly _orcaService: OrcaService;
private readonly _raydiumService: RaydiumService;
private readonly _jupService: JupService;

/**
* Create a new instance of the Kamino SDK class.
Expand Down Expand Up @@ -368,7 +368,6 @@ export class Kamino {

this._orcaService = new OrcaService(connection, cluster, this._globalConfig);
this._raydiumService = new RaydiumService(connection, cluster);
this._jupService = new JupService(connection, cluster);
}

getConnection = () => this._connection;
Expand Down Expand Up @@ -1707,7 +1706,111 @@ export class Kamino {
};
};

getAllPrices = (): Promise<OraclePrices> => this._scope.getOraclePrices();
getAllOraclePrices = (): Promise<OraclePrices> => this._scope.getOraclePrices();

/**
* Get all Kamino token spot and twap prices
* @param oraclePrices (optional) Scope Oracle prices
* @param collateralInfos (optional) Kamino Collateral Infos
*/
getAllPrices = async (oraclePrices?: OraclePrices, collateralInfos?: CollateralInfo[]): Promise<KaminoPrices> => {
const spotPrices: EnrichedScopePrice[] = [];
const twaps: EnrichedScopePrice[] = [];
({ oraclePrices, collateralInfos } = await this.getOraclePricesAndCollateralInfos(oraclePrices, collateralInfos));
for (const collateralInfo of collateralInfos) {
if (collateralInfo.scopePriceChain.some((x) => x > 0)) {
const spotPrice = await this._scope.getPriceFromChain(collateralInfo.scopePriceChain, oraclePrices);
spotPrices.push({
price: spotPrice,
mint: collateralInfo.mint,
name: getTokenNameFromCollateralInfo(collateralInfo),
});

if (collateralInfo.scopeTwapPriceChain.some((x) => x > 0)) {
const twap = await this._scope.getPriceFromChain(collateralInfo.scopeTwapPriceChain, oraclePrices);
twaps.push({ price: twap, mint: collateralInfo.mint, name: getTokenNameFromCollateralInfo(collateralInfo) });
}
}
}
return { spot: spotPrices, twap: twaps };
};

private async getOraclePricesAndCollateralInfos(
oraclePrices?: OraclePrices,
collateralInfos?: CollateralInfo[]
): Promise<OraclePricesAndCollateralInfos> {
if (!oraclePrices) {
oraclePrices = await this.getAllOraclePrices();
}
if (!collateralInfos) {
collateralInfos = await this.getCollateralInfos();
}
return { oraclePrices, collateralInfos };
}

/**
* Get Kamino token spot price by mint
* @param mint token mint
* @param oraclePrices (optional) Scope Oracle prices
* @param collateralInfos (optional) Kamino Collateral Infos
*/
getPriceByMint = async (
mint: PublicKey | string,
oraclePrices?: OraclePrices,
collateralInfos?: CollateralInfo[]
): Promise<EnrichedScopePrice | undefined> => {
return (await this.getAllPrices(oraclePrices, collateralInfos)).spot.find(
(x) => x.mint.toString() === mint.toString()
);
};

/**
* Get Kamino token TWAP by token mint
* @param mint token mint
* @param oraclePrices (optional) Scope Oracle prices
* @param collateralInfos (optional) Kamino Collateral Infos
*/
getTwapByMint = async (
mint: PublicKey | string,
oraclePrices?: OraclePrices,
collateralInfos?: CollateralInfo[]
): Promise<EnrichedScopePrice | undefined> => {
return (await this.getAllPrices(oraclePrices, collateralInfos)).twap.find(
(x) => x.mint.toString() === mint.toString()
);
};

/**
* Get multiple Kamino token spot price by mint
* @param mints token mint
* @param oraclePrices (optional) Scope Oracle prices
* @param collateralInfos (optional) Kamino Collateral Infos
*/
getPricesByMints = async (
mints: (PublicKey | string)[],
oraclePrices?: OraclePrices,
collateralInfos?: CollateralInfo[]
): Promise<(EnrichedScopePrice | undefined)[]> => {
return (await this.getAllPrices(oraclePrices, collateralInfos)).spot.filter((x) =>
mints.some((y) => y.toString() === x.mint.toString())
);
};

/**
* Get multiple Kamino token TWAP by token mint
* @param mints token mints
* @param oraclePrices (optional) Scope Oracle prices
* @param collateralInfos (optional) Kamino Collateral Infos
*/
getTwapsByMints = async (
mints: (PublicKey | string)[],
oraclePrices?: OraclePrices,
collateralInfos?: CollateralInfo[]
): Promise<(EnrichedScopePrice | undefined)[]> => {
return (await this.getAllPrices(oraclePrices, collateralInfos)).twap.filter((x) =>
mints.some((y) => y.toString() === x.mint.toString())
);
};

/**
* Get the prices of all tokens in the specified strategy, or null if the reward token does not exist
Expand Down Expand Up @@ -4952,7 +5055,7 @@ export class Kamino {
};
}
if (isOrca) {
const prices = await this.getAllPrices();
const prices = await this.getAllOraclePrices();
return this._orcaService.getStrategyWhirlpoolPoolAprApy(strategyState, orcaPools, prices);
}
if (isRaydium) {
Expand Down
29 changes: 29 additions & 0 deletions packages/kamino-sdk/src/models/EnrichedScopePrice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Decimal from 'decimal.js';
import { PublicKey } from '@solana/web3.js';
import { OraclePrices } from '@hubbleprotocol/scope-sdk';
import { CollateralInfo } from '../kamino-client/types';

export interface EnrichedScopePrice {
/**
* Price in USD
*/
price: Decimal;
/**
* Token mint
*/
mint: PublicKey;
/**
* Token name (as specified in collateral infos)
*/
name: string;
}

export interface KaminoPrices {
spot: EnrichedScopePrice[];
twap: EnrichedScopePrice[];
}

export interface OraclePricesAndCollateralInfos {
oraclePrices: OraclePrices;
collateralInfos: CollateralInfo[];
}
53 changes: 53 additions & 0 deletions packages/kamino-sdk/tests/kamino.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,59 @@ describe('Kamino SDK Tests', () => {
expect(init).to.throw(Error);
});

it('should get all Kamino prices', async () => {
const kamino = new Kamino(
cluster,
connection,
fixtures.globalConfig,
fixtures.kaminoProgramId,
WHIRLPOOL_PROGRAM_ID,
LOCAL_RAYDIUM_PROGRAM_ID
);
const prices = await kamino.getAllPrices();
expect(prices).not.to.be.undefined;
expect(prices.spot).to.have.length(2);

expect(prices.twap).to.have.length(2);

const usdh = prices.spot.find((x) => x.mint.toString() === fixtures.newTokenMintA.toString());
const usdc = prices.spot.find((x) => x.mint.toString() === fixtures.newTokenMintB.toString());
const usdhTwap = prices.twap.find((x) => x.mint.toString() === fixtures.newTokenMintA.toString());
const usdcTwap = prices.twap.find((x) => x.mint.toString() === fixtures.newTokenMintB.toString());
expect(usdh).not.to.be.undefined;
expect(usdh!.name).to.be.equal('USDH');
expect(usdh!.price.toNumber()).to.be.greaterThan(0);
expect(usdc).not.to.be.undefined;
expect(usdc!.name).to.be.equal('USDC');
expect(usdc!.price.toNumber()).to.be.greaterThan(0);
expect(usdhTwap).not.to.be.undefined;
expect(usdhTwap!.name).to.be.equal('USDH');
expect(usdhTwap!.price.toNumber()).to.be.greaterThan(0);
expect(usdcTwap).not.to.be.undefined;
expect(usdcTwap!.name).to.be.equal('USDC');
expect(usdcTwap!.price.toNumber()).to.be.greaterThan(0);
});

it('should get Kamino price and twap by mint', async () => {
const kamino = new Kamino(
cluster,
connection,
fixtures.globalConfig,
fixtures.kaminoProgramId,
WHIRLPOOL_PROGRAM_ID,
LOCAL_RAYDIUM_PROGRAM_ID
);
const usdh = await kamino.getPriceByMint(fixtures.newTokenMintA);
expect(usdh).not.to.be.undefined;
expect(usdh!.name).to.be.equal('USDH');
expect(usdh!.price.toNumber()).to.be.greaterThan(0);

const usdhTwap = await kamino.getTwapByMint(fixtures.newTokenMintA);
expect(usdhTwap).not.to.be.undefined;
expect(usdhTwap!.name).to.be.equal('USDH');
expect(usdhTwap!.price.toNumber()).to.be.greaterThan(0);
});

it('should get all strategies', async () => {
let kamino = new Kamino(
cluster,
Expand Down

0 comments on commit 0c6049d

Please sign in to comment.