UNPKG

@hubbleprotocol/hubble-sdk

Version:
750 lines (702 loc) 31.8 kB
import { getConfigByCluster, HubbleConfig, SolanaCluster } from '@hubbleprotocol/hubble-config'; import { Connection, PublicKey, SYSVAR_RENT_PUBKEY, SystemProgram, TransactionInstruction } from '@solana/web3.js'; import StakingPoolState from './models/StakingPoolState'; import StabilityPoolState from './models/StabilityPoolState'; import BorrowingMarketState from './models/BorrowingMarketState'; import { Idl, Program, AnchorProvider } from '@coral-xyz/anchor'; import BorrowingIdl from './borrowing.json'; import { calculatePendingGains, calculateStabilityProvided, calculateTotalCollateral, calculateTotalDebt, decimalToNumCollateralWithdrawalCap, decimalToNumSupportedCollateral, decimalToNumWithdrawalCap, getReadOnlyWallet, isZero, replaceBigNumberWithDecimal, } from './utils'; import UserStakingState from './models/UserStakingState'; import StabilityProviderState from './models/StabilityProviderState'; import UserMetadata from './models/UserMetadata'; import Loan from './models/Loan'; import { DECIMAL_FACTOR, ExtraCollateralMap, HBB_DECIMALS, STABLECOIN_DECIMALS, STREAMFLOW_HBB_CONTRACT, } from './constants'; import Decimal from 'decimal.js'; import UserMetadataWithJson from './models/UserMetadataWithJson'; import { StreamflowSolana, StreamType, StreamDirection, ICluster } from '@streamflow/stream'; import StabilityProviderStateWithJson from './models/StabilityProviderStateWithJson'; import { HbbVault, HubblePrices, MintToPriceMap, PsmReserve, UsdhVault } from './models'; import GlobalConfig from './models/GlobalConfig'; import { SwapInfo } from './models/SwapInfo'; import { signTerms, SignTermsAccounts, SignTermsArgs, TermsSignature } from './models/TermsSignature'; import { OraclePrices, Scope } from '@kamino-finance/scope-sdk'; export class Hubble { private readonly _cluster: SolanaCluster; private readonly _connection: Connection; private readonly _scope: Scope; readonly _config: HubbleConfig; private readonly _provider: AnchorProvider; private _borrowingProgram: Program; /** * Create a new instance of the Hubble SDK class. * @param cluster Name of the Solana cluster * @param connection Connection to the Solana cluster */ constructor(cluster: SolanaCluster, connection: Connection, borrowingProgramId?: string) { this._cluster = cluster; this._connection = connection; this._scope = new Scope(cluster, connection); this._config = getConfigByCluster(cluster); // for localnet integration tests if (borrowingProgramId) { this._config.borrowing.programId = new PublicKey(borrowingProgramId); } this._provider = new AnchorProvider(connection, getReadOnlyWallet(), { commitment: connection.commitment, }); this._borrowingProgram = new Program(BorrowingIdl as Idl, this._config.borrowing.programId, this._provider); } /** * Get Hubble's staking pool state. * @return on-chain {@link StakingPoolState} from the borrowing program with numbers as lamports */ async getStakingPoolState(): Promise<StakingPoolState> { const stakingPoolState = (await this._borrowingProgram.account.stakingPoolState.fetch( this._config.borrowing.accounts.stakingPoolState )) as StakingPoolState; return replaceBigNumberWithDecimal(stakingPoolState); } /** * Get Hubble's stability pool state. * @return on-chain {@link StabilityPoolState} from the borrowing program with numbers as lamports */ async getStabilityPoolState(): Promise<StabilityPoolState> { let stability = (await this._borrowingProgram.account.stabilityPoolState.fetch( this._config.borrowing.accounts.stabilityPoolState )) as StabilityPoolState; stability = replaceBigNumberWithDecimal(stability); stability.cumulativeGainsTotal = replaceBigNumberWithDecimal(stability.cumulativeGainsTotal); stability.lastCollLossErrorOffset = replaceBigNumberWithDecimal(stability.lastCollLossErrorOffset); stability.pendingCollateralGains = replaceBigNumberWithDecimal(stability.pendingCollateralGains); return stability; } private borrowingMarketStateToDecimals(state: BorrowingMarketState, pubkey: PublicKey) { state = replaceBigNumberWithDecimal(state); state.depositedCollateral.amounts = replaceBigNumberWithDecimal(state.depositedCollateral.amounts); state.inactiveCollateral.amounts = replaceBigNumberWithDecimal(state.inactiveCollateral.amounts); state.collateralRewardPerToken = replaceBigNumberWithDecimal(state.collateralRewardPerToken); state.withdrawalCap = decimalToNumWithdrawalCap(replaceBigNumberWithDecimal(state.withdrawalCap) as any); state.withdrawalCapsCollateral = state.withdrawalCapsCollateral.map((collCap) => { collCap.tokenCap = decimalToNumWithdrawalCap(replaceBigNumberWithDecimal(collCap.tokenCap) as any); return decimalToNumCollateralWithdrawalCap(replaceBigNumberWithDecimal(collCap as any)); }); state.supportedCollaterals = state.supportedCollaterals.map((collateral) => decimalToNumSupportedCollateral(replaceBigNumberWithDecimal(collateral as any)) ); state.pubkey = pubkey; return state; } /** * @deprecated Deprecated since version 1.0.114. * Please use getBorrowingMarketStateByPubkey or getBorrowingMarketStates and use the correct borrowing market state specified by the UserMetadata. * Get Hubble's borrowing market state. * @return on-chain {@link BorrowingMarketState} from the borrowing program with numbers as lamports */ async getBorrowingMarketState(): Promise<BorrowingMarketState> { console.warn( 'getBorrowingMarketState has been deprecated since version 1.0.114. Please use getBorrowingMarketStates and use the correct borrowing market state specified by the UserMetadata.' ); const state = (await this._borrowingProgram.account.borrowingMarketState.fetch( this._config.borrowing.accounts.borrowingMarketState )) as BorrowingMarketState; return this.borrowingMarketStateToDecimals(state, this._config.borrowing.accounts.borrowingMarketState); } /** * Get Hubble's borrowing market state by public key. * @return on-chain {@link BorrowingMarketState} from the borrowing program with numbers as lamports */ async getBorrowingMarketStateByPubkey(pubkey: PublicKey): Promise<BorrowingMarketState> { const state = (await this._borrowingProgram.account.borrowingMarketState.fetch(pubkey)) as BorrowingMarketState; return this.borrowingMarketStateToDecimals(state, pubkey); } /** * Get Hubble's borrowing market states. * @return list of on-chain {@link BorrowingMarketState} from the borrowing program with numbers as lamports */ async getBorrowingMarketStates(): Promise<BorrowingMarketState[]> { const states: BorrowingMarketState[] = []; for (const pubkey of this._config.borrowing.accounts.borrowingMarketStates) { const state = (await this._borrowingProgram.account.borrowingMarketState.fetch(pubkey)) as BorrowingMarketState; states.push(this.borrowingMarketStateToDecimals(state, pubkey)); } return states; } /** * Get user's staking state (staking stats). * @param user Base58 encoded Public Key of the user * @return on-chain {@link UserStakingState} from the borrowing program for the specific user with numbers as lamports * or undefined if user has never used Hubble before or authorized HBB staking */ async getUserStakingState(user: PublicKey | string): Promise<UserStakingState | undefined> { const userStakingStates = ( await this._borrowingProgram.account.userStakingState.all([ { memcmp: { bytes: user instanceof PublicKey ? user.toBase58() : user, offset: 49, // 8 (account discriminator for user staking state) + 1 (version u8) + 8 (user_id u64) + 32 (staking_pool_state pubkey [u8, 32]) }, }, { memcmp: { bytes: this._config.borrowing.accounts.stakingPoolState.toBase58(), offset: 17, // 8 (account discriminator for user staking state) + 1 (version u8) + 8 (user_id u64) }, }, ]) ).map((x) => replaceBigNumberWithDecimal(x.account as UserStakingState)); return userStakingStates.find((x) => !x.userStake.isZero()); } /** * Convert anchor's stability provider state with BN to stability provider state with decimals * @param stabilityProviderState */ private static stabilityProviderStateToDecimals(stabilityProviderState: StabilityProviderState) { const converted = replaceBigNumberWithDecimal(stabilityProviderState); converted.userDepositSnapshot = replaceBigNumberWithDecimal(converted.userDepositSnapshot); converted.userDepositSnapshot.sum = replaceBigNumberWithDecimal(converted.userDepositSnapshot.sum); converted.pendingGainsPerUser = replaceBigNumberWithDecimal(converted.pendingGainsPerUser); converted.cumulativeGainsPerUser = replaceBigNumberWithDecimal(converted.cumulativeGainsPerUser); return converted; } /** * Get user's stability provider state (stability pool stats). * @param user Base58 encoded Public Key of the user * @return on-chain {@link StabilityProviderState} from the borrowing program for the specific user with numbers as lamports. * Returns undefined if this user has never used Hubble Stability pool before and does not exist in Hubble on-chain data */ async getUserStabilityProviderState(user: PublicKey | string): Promise<StabilityProviderState | undefined> { const stabilityProviderStates = ( await this._borrowingProgram.account.stabilityProviderState.all([ { memcmp: { bytes: user instanceof PublicKey ? user.toBase58() : user, offset: 41, // 8 (account discriminator for stability provider state) + 1 (version u8) + 32 (stability pool state pubkey [u8, 32]) }, }, { memcmp: { bytes: this._config.borrowing.accounts.stabilityPoolState.toBase58(), offset: 9, // 8 (account discriminator for stability provider state) + 1 (version u8) }, }, ]) ).map((x) => Hubble.stabilityProviderStateToDecimals(x.account as StabilityProviderState)); return stabilityProviderStates.find((x) => !x.depositedStablecoin.isZero()); } /** * Get all Hubble stability providers (stability pool stats). * @return list of on-chain {@link StabilityProviderState} from the borrowing program */ async getStabilityProviders(): Promise<StabilityProviderState[]> { return ( await this._borrowingProgram.account.stabilityProviderState.all([ { memcmp: { bytes: this._config.borrowing.accounts.stabilityPoolState.toBase58(), offset: 9, // 8 (account discriminator for stability provider state) + 1 (version u8) }, }, ]) ).map((x) => Hubble.stabilityProviderStateToDecimals(x.account as StabilityProviderState)); } /** * Get all non-zero Hubble user staking states. * @return list of on-chain {@link UserStakingState} from the borrowing program */ async getUserStakingStates(): Promise<UserStakingState[]> { const userStakingStates = ( await this._borrowingProgram.account.userStakingState.all([ { memcmp: { bytes: this._config.borrowing.accounts.stakingPoolState.toBase58(), offset: 17, // 8 (account discriminator for user staking state) + 1 (version u8) + 8 (user_id u64) }, }, ]) ).map((x) => replaceBigNumberWithDecimal(x.account as UserStakingState)); return userStakingStates.filter((x) => !x.userStake.isZero()); } /** * Get all Hubble stability providers (stability pool stats) and include raw JSON RPC responses in the return value. * @return list of on-chain {@link StabilityProviderStateWithJson} from the borrowing program */ async getStabilityProvidersIncludeJsonResponse(): Promise<StabilityProviderStateWithJson[]> { return ( await this._borrowingProgram.account.stabilityProviderState.all([ { memcmp: { bytes: this._config.borrowing.accounts.stabilityPoolState.toBase58(), offset: 9, // 8 (account discriminator for stability provider state) + 1 (version u8) }, }, ]) ).map((x) => { const stabilityProvider = Hubble.stabilityProviderStateToDecimals( x.account as StabilityProviderState ) as StabilityProviderStateWithJson; stabilityProvider.jsonResponse = JSON.stringify(x.account); return stabilityProvider; }); } /** * Convert user metadata BN fields to Decimal * @param user * @private */ private static userMetadataToDecimals(user: UserMetadata) { const converted: UserMetadata = replaceBigNumberWithDecimal(user); converted.userCollateralRewardPerToken = replaceBigNumberWithDecimal(converted.userCollateralRewardPerToken); converted.depositedCollateral = replaceBigNumberWithDecimal(converted.depositedCollateral); converted.inactiveCollateral = replaceBigNumberWithDecimal(converted.inactiveCollateral); converted.depositedCollateral.extraCollaterals = converted.depositedCollateral.extraCollaterals.map((x) => replaceBigNumberWithDecimal(x) ); converted.inactiveCollateral.extraCollaterals = converted.inactiveCollateral.extraCollaterals.map((x) => replaceBigNumberWithDecimal(x) ); return replaceBigNumberWithDecimal(converted); } /** * Get all of user's metadatas (borrowing state, debt, collateral stats...), user can have multiple borrowing accounts. * @param user Base58 encoded Public Key of the user * @return on-chain {@link UserMetadata} from the borrowing program for the specific user with numbers as lamports */ async getUserMetadatas(user: PublicKey | string): Promise<UserMetadata[]> { const result: UserMetadata[] = []; for (const bms of this._config.borrowing.accounts.borrowingMarketStates) { const userMetadatas = ( await this._borrowingProgram.account.userMetadata.all([ { memcmp: { bytes: user instanceof PublicKey ? user.toBase58() : user, offset: 50, // 8 (account discriminator for usermetadata) + 1 (version u8) + 1 (status u8) + 8 (user_id u64) + 32 (metadata_pk pubkey [u8, 32]) }, }, { memcmp: { bytes: bms.toBase58(), offset: 82, // 8 (account discriminator for usermetadata) + 1 (version u8) + 1 (status u8) + 8 (user_id u64) + 32 (metadata_pk pubkey [u8, 32]) + 32 (owner pubkey) }, }, ]) ).map((x) => Hubble.userMetadataToDecimals(x.account as UserMetadata)); result.push(...userMetadatas); } return result; } /** * Get specific user metadata (borrowing state, debt, collateral stats...). * @param metadata Base58 encoded Public Key of the user metadata * @return on-chain {@link UserMetadata} from the borrowing program for the specific user with numbers as lamports */ async getUserMetadata(metadata: PublicKey | string): Promise<UserMetadata> { return Hubble.userMetadataToDecimals( (await this._borrowingProgram.account.userMetadata.fetch(metadata)) as UserMetadata ); } /** * Get all Hubble user metadatas (borrowing state, debt, collateral stats...), one user can have multiple borrowing accounts. * @return list of on-chain {@link UserMetadata} from the borrowing program for the specific user with numbers as lamports */ async getAllUserMetadatas(): Promise<UserMetadata[]> { const result: UserMetadata[] = []; for (const bms of this._config.borrowing.accounts.borrowingMarketStates) { const userMetadatas = ( await this._borrowingProgram.account.userMetadata.all([ { memcmp: { bytes: bms.toBase58(), offset: 82, // 8 (account discriminator for usermetadata) + 1 (version u8) + 1 (status u8) + 8 (user_id u64) + 32 (metadata_pk pubkey [u8, 32]) + 32 (owner pubkey) }, }, ]) ).map((x) => Hubble.userMetadataToDecimals(x.account as UserMetadata)); result.push(...userMetadatas); } return result; } /** * Get all Hubble user metadatas (borrowing state, debt, collateral stats...) and include raw JSON RPC responses in the return value. * @return list of on-chain {@link UserMetadata} from the borrowing program for the specific user with numbers as lamports */ async getAllUserMetadatasIncludeJsonResponse(): Promise<UserMetadataWithJson[]> { const result: UserMetadataWithJson[] = []; for (const bms of this._config.borrowing.accounts.borrowingMarketStates) { const userMetadatas = ( await this._borrowingProgram.account.userMetadata.all([ { memcmp: { bytes: bms.toBase58(), offset: 82, // 8 (account discriminator for usermetadata) + 1 (version u8) + 1 (status u8) + 8 (user_id u64) + 32 (metadata_pk pubkey [u8, 32]) + 32 (owner pubkey) }, }, ]) ).map((x) => { const userMetadata = Hubble.userMetadataToDecimals(x.account as UserMetadata) as UserMetadataWithJson; userMetadata.jsonResponse = JSON.stringify(x.account); return userMetadata; }); result.push(...userMetadatas); } return result; } /** * Get user's loans. Fetches all {@link UserMetadata} of the specified user and converts it to a human-friendly list of {@link Loan}. * @param user Base58 encoded Public Key of the user * @return User's loans with already converted on-chain data (from lamports to decimal values) */ async getUserLoans(user: PublicKey | string): Promise<Loan[]> { const loans: Loan[] = []; const userVaults = await this.getUserMetadatas(user); if (userVaults.length === 0) { return []; } const borrowingMarketStates = await this.getBorrowingMarketStates(); for (const userVault of userVaults) { let borrowingMarketState = borrowingMarketStates.find( (x) => x.pubkey.toBase58() === userVault.borrowingMarketState.toBase58() ); if (!borrowingMarketState) { borrowingMarketState = await this.getBorrowingMarketStateByPubkey(userVault.borrowingMarketState); borrowingMarketStates.push(borrowingMarketState); } const loan: Loan = { usdhDebt: calculateTotalDebt(userVault, borrowingMarketState), collateral: calculateTotalCollateral(userVault, borrowingMarketState), }; if (loan.usdhDebt.greaterThan(0) || !isZero(loan.collateral)) { loans.push(loan); } } return loans; } /** * Get user's deposited stablecoin (USDH) in the stability pool. * @param user Base58 encoded Public Key of the user * @return Deposited stablecoin (USDH) in decimal format or * undefined if user has never used Hubble before or authorized stability pool deposits */ async getUserUsdhInStabilityPool(user: PublicKey | string): Promise<Decimal | undefined> { const provider = await this.getUserStabilityProviderState(user); if (provider) { const pool = await this.getStabilityPoolState(); return calculateStabilityProvided(pool, provider).dividedBy(STABLECOIN_DECIMALS); } return undefined; } /** * Get user's USDH vault (usdh staked + liquidation rewards + hbb rewards) * @param user Base58 encoded Public Key of the user * @return USDH vault with amount of USDH staked, liquidation rewards and HBB rewards or * undefined if user has never used Hubble before or authorized stability pool deposits */ async getUserUsdhVault(user: PublicKey | string): Promise<UsdhVault | undefined> { const provider = await this.getUserStabilityProviderState(user); if (provider) { const pool = await this.getStabilityPoolState(); const epoch = await this.getEpochToScaleToSum(); const usdhStaked = calculateStabilityProvided(pool, provider).dividedBy(STABLECOIN_DECIMALS); const gains = calculatePendingGains(pool, provider, epoch); return { usdhStaked, hbbRewards: gains.hbb, liquidationRewards: { sol: gains.sol, eth: gains.eth, ftt: gains.ftt, btc: gains.btc, ray: gains.ray, srm: gains.srm, msol: gains.msol, extraCollaterals: [], // user does not gain any liquidation rewards from extra collateral }, }; } return undefined; } /** * Get a list of epoch to scale to sum values for Hubble * @return Array of epoch to scale to sum in decimal format */ async getEpochToScaleToSum() { const epoch = await this._borrowingProgram.account.epochToScaleToSumAccount.fetch( this._config.borrowing.accounts.epochToScaleToSum ); if (epoch) { return replaceBigNumberWithDecimal(epoch.data) as Decimal[]; } throw Error(`Could not get epoch to scale to sum values from ${this._config.borrowing.accounts.epochToScaleToSum}`); } /** * Get the amount of staked HBB of a specific user. * @param user Base58 encoded Public Key of the user * @return HBB staked in decimal format or * undefined if user has never used Hubble before or authorized HBB staking */ async getUserStakedHbb(user: PublicKey | string): Promise<Decimal | undefined> { const stakingState = await this.getUserStakingState(user); if (stakingState) { return stakingState.userStake.dividedBy(HBB_DECIMALS); } return undefined; } /** * Get the user's HBB vault (HBB staked + USDH rewards) * @param user Base58 encoded Public Key of the user * @return HBB vault with number of HBB staked and USDH rewards or * undefined if user has never used Hubble before or authorized HBB staking */ async getUserHbbVault(user: PublicKey | string): Promise<HbbVault | undefined> { const stakingState = await this.getUserStakingState(user); if (stakingState) { const stakingPoolState = await this.getStakingPoolState(); const usdhRewards = new Decimal( stakingState.userStake.mul(stakingPoolState.rewardPerToken).minus(stakingState.rewardsTally) ) .div(DECIMAL_FACTOR) .div(STABLECOIN_DECIMALS); return { hbbStaked: stakingState.userStake.dividedBy(HBB_DECIMALS), usdhRewards, }; } return undefined; } /** * Get Hubble's treasury vault value * @return Value of Hubble's treasury vault in decimal representation */ async getTreasuryVault() { const acccountBalance = await this._provider.connection.getTokenAccountBalance( this._config.borrowing.accounts.treasuryVault! ); if (!acccountBalance.value.uiAmountString) { throw Error( `Could not get account balance of Hubble treasury vault: ${this._config.borrowing.accounts.treasuryVault}` ); } return new Decimal(acccountBalance.value.uiAmountString); } /** * Get circulating supply number of the Hubble (HBB) token. * This also takes into account the locked HBB inside Streamflow vesting contracts and subtracts the locked HBB amount. * @return Number of HBB in circulation in decimal representation */ async getHbbCirculatingSupply() { const tokenSupply = await this._provider.connection.getTokenSupply(this._config.borrowing.accounts.mint.HBB); if (!tokenSupply.value.uiAmountString) { throw Error( `Could not get HBB circulating supply from the HBB mint account: ${this._config.borrowing.accounts.mint.HBB}` ); } let totalTokenSupply = new Decimal(tokenSupply.value.uiAmountString); if (this._cluster === 'mainnet-beta') { try { const client = new StreamflowSolana.SolanaStreamClient(this._connection.rpcEndpoint, ICluster.Mainnet); const streams = await client.get({ address: STREAMFLOW_HBB_CONTRACT, type: StreamType.All, direction: StreamDirection.All, }); let notWithdrawnTokens = new Decimal(0); for (let [pubkey, stream] of streams) { const totalWithdrawn = new Decimal(stream.withdrawnAmount.toString()).add( stream.streamflowFeeWithdrawn.toString() ); const deposited = new Decimal(stream.depositedAmount.toString()); notWithdrawnTokens = notWithdrawnTokens.add(deposited.minus(totalWithdrawn).dividedBy(HBB_DECIMALS)); } totalTokenSupply = totalTokenSupply.minus(notWithdrawnTokens); } catch (exception) { throw Error(`Could not get HBB Streamflow contract data: ${exception}`); } } return totalTokenSupply; } async getUsdhCirculatingSupply() { const tokenSupply = await this._provider.connection.getTokenSupply(this._config.borrowing.accounts.stablecoinMint); if (!tokenSupply.value.uiAmountString) { throw Error( `Could not get USDH circulating supply from the USDH mint account: ${this._config.borrowing.accounts.stablecoinMint}` ); } return new Decimal(tokenSupply.value.uiAmountString); } /** * Get all token accounts that are holding HBB */ getHbbTokenAccounts() { //how to get all token accounts for specific mint: https://spl.solana.com/token#finding-all-token-accounts-for-a-specific-mint //get it from the hardcoded token program and create a filter with the actual mint address //datasize:165 filter selects all token accounts, memcmp filter selects based on the mint address withing each token account const tokenProgram = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); return this._provider.connection.getParsedProgramAccounts(tokenProgram, { filters: [ { dataSize: 165 }, { memcmp: { offset: 0, bytes: this._config.borrowing.accounts.mint.HBB.toBase58() } }, ], }); } /** * Get Hubble's global config values */ async getGlobalConfig(): Promise<GlobalConfig> { let globalConfig = (await this._borrowingProgram.account.globalConfig.fetch( this._config.borrowing.accounts.globalConfig! )) as GlobalConfig; globalConfig = replaceBigNumberWithDecimal(globalConfig); return globalConfig; } /** * Get PSM reserve state */ async getPsmReserve(): Promise<PsmReserve> { const psmPubkey = await this.getPsmPublicKey(); let reserve = (await this._borrowingProgram.account.psmReserve.fetch(psmPubkey)) as PsmReserve; reserve.withdrawalCapStable = decimalToNumWithdrawalCap( replaceBigNumberWithDecimal(reserve.withdrawalCapStable) as any ); reserve.withdrawalCapUsdh = decimalToNumWithdrawalCap( replaceBigNumberWithDecimal(reserve.withdrawalCapUsdh) as any ); reserve = replaceBigNumberWithDecimal(reserve); reserve.mintFeeBps = (reserve.mintFeeBps as any).toNumber(); reserve.burnFeeBps = (reserve.burnFeeBps as any).toNumber(); return reserve; } /** * Get PSM public key */ async getPsmPublicKey(): Promise<PublicKey> { const res = await PublicKey.findProgramAddress( [Buffer.from('PSM'), this._config.borrowing.accounts.USDC!.toBuffer()], this._config.borrowing.programId ); return res[0]; } /** * Get the USDC -> USDH swap information * @param usdcInAmount number of USDC tokens * @param slippage */ async getUsdcToUsdhSwap(usdcInAmount: Decimal, slippage: Decimal = new Decimal(0)): Promise<SwapInfo> { const psmReserve = await this.getPsmReserve(); // we would be minting USDH with this operation // remaining USDC = max capacity - deposited, // this is the amount of USDC that can be stored inside the PSM reserve // we can only mint max this much USDH const availableUsdc = psmReserve.maxCapacity .dividedBy(10 ** psmReserve.stablecoinMintDecimals) .minus(psmReserve.depositedStablecoin.dividedBy(10 ** psmReserve.stablecoinMintDecimals)); let outAmount = usdcInAmount; if (usdcInAmount.greaterThan(availableUsdc)) { outAmount = new Decimal(0); } return { fees: new Decimal(0), // TODO after fees are implemented slippage: new Decimal(0), // TODO: there is no slippage (currently) with PSM inAmount: usdcInAmount, outAmount, }; } /** * Get the USDH -> USDC swap information * @param usdhInAmount number of USDH tokens * @param slippage */ async getUsdhToUsdcSwap(usdhInAmount: Decimal, slippage: Decimal = new Decimal(0)): Promise<SwapInfo> { const psmReserve = await this.getPsmReserve(); let outAmount = new Decimal(0); // we are burning USDH with this operation and we can only burn as much as there is deposited_stablecoin inside psm reserve const usdcAvailable = psmReserve.depositedStablecoin.dividedBy(10 ** psmReserve.stablecoinMintDecimals); if (usdhInAmount.lessThanOrEqualTo(usdcAvailable)) { outAmount = usdhInAmount; } return { fees: new Decimal(0), // TODO: after fees are implemented slippage: new Decimal(0), // TODO: there is no slippage (currently) with PSM inAmount: usdhInAmount, outAmount, }; } /** * Get the instruction to store the on chain owner signature of terms&conditions * @param owner * @param signature */ async getUserTermsSignatureIx(owner: PublicKey, signature: Uint8Array): Promise<TransactionInstruction> { const pdaSeed = [Buffer.from('signature'), owner.toBuffer()]; const [signatureStateKey, _signatureStateBump] = PublicKey.findProgramAddressSync( pdaSeed, this._config.borrowing.programId ); const args: SignTermsArgs = { signature: Array.from(signature), }; const accounts: SignTermsAccounts = { owner: owner, ownerSignatureState: signatureStateKey, systemProgram: SystemProgram.programId, rent: SYSVAR_RENT_PUBKEY, }; return signTerms(args, accounts, this._config.borrowing.programId); } /** * Get the on-chain state of the terms&conditions signature for the owner * @param owner */ async getUserTermsSignatureState(owner: PublicKey): Promise<TermsSignature | null> { const pdaSeed = [Buffer.from('signature'), owner.toBuffer()]; const [signatureStateKey, _signatureStateBump] = PublicKey.findProgramAddressSync( pdaSeed, this._config.borrowing.programId ); return await TermsSignature.fetch(this._connection, signatureStateKey, this._config.borrowing.programId); } /** * Get all Scope prices used by Hubble */ async getAllPrices(oraclePrices?: OraclePrices): Promise<HubblePrices> { if (!oraclePrices) { oraclePrices = await this._scope.getOraclePrices(); } const spotPrices: MintToPriceMap = {}; const twaps: MintToPriceMap = {}; for (const collateral of ExtraCollateralMap.filter((x) => x.mint !== undefined)) { if (collateral.scopePriceChain && Scope.isScopeChainValid(collateral.scopePriceChain)) { const spotPrice = await this._scope.getPriceFromChain(collateral.scopePriceChain, oraclePrices); spotPrices[collateral.mint!.toString()] = { price: spotPrice.price, name: collateral.name, }; const filteredTwapChain = collateral?.scopeTwapChain?.filter((x) => x > 0); if (filteredTwapChain && Scope.isScopeChainValid(filteredTwapChain)) { const twap = await this._scope.getPriceFromChain(filteredTwapChain, oraclePrices); twaps[collateral.mint!.toString()] = { price: twap.price, name: collateral.name, }; } } } return { spot: spotPrices, twap: twaps }; } } export default Hubble;