Skip to content

Commit

Permalink
Add withdrawable yield balance check solana function
Browse files Browse the repository at this point in the history
  • Loading branch information
mliu committed Jan 7, 2025
1 parent dc79163 commit 6279a68
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 89 deletions.
1 change: 1 addition & 0 deletions packages/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"solanaFetchReceivable": "ts-node src/solana/fetchReceivable.ts",
"solanaFetchAvailableCredit": "ts-node src/solana/fetchAvailableCredit.ts",
"solanaFetchBorrowerDetails": "ts-node src/solana/fetchBorrowerDetails.ts",
"solanaCheckWithdrawableYield": "ts-node src/solana/checkWithdrawableYield.ts",
"solanaDrawdown": "ts-node src/solana/drawdown.ts",
"solanaWithdrawYield": "ts-node src/solana/withdrawYield.ts",
"solanaPayback": "ts-node src/solana/payback.ts",
Expand Down
43 changes: 43 additions & 0 deletions packages/examples/src/solana/checkWithdrawableYield.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import {
AnchorProvider,
BN,
setProvider,
Wallet,
web3,
} from '@coral-xyz/anchor'
import { POOL_NAME, SolanaChainEnum } from '@huma-finance/shared'
import { HumaSolanaContext, HumaSolanaProgramHelper } from '@huma-finance/sdk'

require('dotenv').config()

async function main() {
const TEST_PRIVATE_KEY = process.env.TEST_PRIVATE_KEY
const connection = new Connection(
'https://api.mainnet-beta.solana.com',
'confirmed',
)

const keypair = web3.Keypair.fromSecretKey(
Buffer.from(JSON.parse(TEST_PRIVATE_KEY)),
)
const wallet = new Wallet(keypair)
setProvider(new AnchorProvider(connection, wallet))

const solanaHumaContext = new HumaSolanaContext({
publicKey: new PublicKey('C8E8YuXRzdiswUxbyh8aampqBz9pmEscw57S5JM99Li8'),
connection: connection,
chainId: SolanaChainEnum.SolanaMainnet,
poolName: POOL_NAME.ArfCreditPool6Months,
})

const humaSolanaProgramHelper = new HumaSolanaProgramHelper({
solanaContext: solanaHumaContext,
})

const data = await humaSolanaProgramHelper.getWithdrawableYields()

console.log(data)
}

main()
1 change: 0 additions & 1 deletion packages/huma-sdk/src/graphql/generatedTypes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GraphQLClient } from 'graphql-request'
import * as Dom from 'graphql-request/dist/types.dom'
import gql from 'graphql-tag'

export type Maybe<T> = T | null
Expand Down
111 changes: 111 additions & 0 deletions packages/huma-sdk/src/helpers/solana/HumaSolanaProgramHelper.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { BN, utils } from '@coral-xyz/anchor'
import {
convertToAssets,
getCreditAccounts,
getHumaProgram,
getSentinelAddress,
getSolanaPoolInfo,
getTokenAccounts,
SolanaTokenUtils,
TrancheType,
} from '@huma-finance/shared'
import {
createApproveCheckedInstruction,
createAssociatedTokenAccountInstruction,
getAccount,
getAssociatedTokenAddress,
getMint,
TOKEN_2022_PROGRAM_ID,
TOKEN_PROGRAM_ID,
TokenAccountNotFoundError,
Expand All @@ -23,6 +27,8 @@ import { HumaSolanaContext } from './HumaSolanaContext'
export const MPL_CORE_PROGRAM_ID =
'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d'

const DEFAULT_DECIMALS_FACTOR = BigInt('1000000000000000000')

export class HumaSolanaProgramHelper {
#solanaContext: HumaSolanaContext

Expand Down Expand Up @@ -128,6 +134,111 @@ export class HumaSolanaProgramHelper {
return tx
}

async getWithdrawableYieldOfTranche(
trancheMint: PublicKey,
trancheType: TrancheType,
trancheAssets: BN[],
): Promise<bigint> {
const { connection, chainId, poolName, publicKey } = this.#solanaContext
const program = getHumaProgram(chainId, connection)
const poolInfo = getSolanaPoolInfo(chainId, poolName)

if (!poolInfo) {
throw new Error(`Could not find pool ${poolName}`)
}

const mintAccount = await getMint(
connection,
trancheMint,
undefined,
TOKEN_2022_PROGRAM_ID,
)
const trancheATA = await getAssociatedTokenAddress(
trancheMint,
publicKey,
true, // allowOwnerOffCurve
TOKEN_2022_PROGRAM_ID,
)
let trancheTokenAccount
try {
trancheTokenAccount = await getAccount(
connection,
trancheATA,
undefined, // commitment
TOKEN_2022_PROGRAM_ID,
)
} catch (error) {
console.log(
`Couldn't find token account for ${trancheATA.toString()} tranche ${trancheType}`,
)
return BigInt(0)
}

const [lenderStatePDA] = PublicKey.findProgramAddressSync(
[
Buffer.from('lender_state'),
trancheMint.toBuffer(),
publicKey.toBuffer(),
],
program.programId,
)
const lenderState = await program.account.lenderState.fetchNullable(
lenderStatePDA,
)

if (!lenderState) {
return BigInt(0)
}

// Scale numbers using `DEFAULT_DECIMALS_FACTOR` to reduce precision loss caused by
// integer division.
const priceWithDecimals = convertToAssets(
BigInt(trancheAssets[trancheType === 'junior' ? 0 : 1].toString()),
mintAccount.supply,
DEFAULT_DECIMALS_FACTOR,
)
const assetsWithDecimals =
priceWithDecimals * BigInt(trancheTokenAccount.amount.toString())
const principalWithDecimals =
BigInt(lenderState.depositRecord.principal.toString()) *
DEFAULT_DECIMALS_FACTOR
const yieldsWithDecimals = assetsWithDecimals - principalWithDecimals
return yieldsWithDecimals / DEFAULT_DECIMALS_FACTOR
}

// Returns the withdrawable yields for the lender for the junior and senior tranche, if applicable
async getWithdrawableYields(): Promise<[bigint, bigint]> {
const { connection, chainId, poolName } = this.#solanaContext
const program = getHumaProgram(chainId, connection)
const poolInfo = getSolanaPoolInfo(chainId, poolName)

if (!poolInfo) {
throw new Error('Could not find pool')
}

const poolStateAccountResult = await program.account.poolState.fetch(
new PublicKey(poolInfo.poolState),
)

const juniorYield = await this.getWithdrawableYieldOfTranche(
new PublicKey(poolInfo.juniorTrancheMint),
'junior',
poolStateAccountResult.trancheAssets.assets,
)

if (!poolInfo.seniorTrancheMint) {
return [juniorYield, BigInt(0)]
}

const seniorYield = await this.getWithdrawableYieldOfTranche(
new PublicKey(poolInfo.seniorTrancheMint),
'senior',
poolStateAccountResult.trancheAssets.assets,
)

return [juniorYield, seniorYield]
}

async buildWithdrawYieldsTransaction(): Promise<Transaction> {
const { publicKey, connection, chainId, poolName } = this.#solanaContext
const program = getHumaProgram(chainId, connection)
Expand Down
12 changes: 12 additions & 0 deletions packages/huma-shared/src/solana/utils/tokenAssetsSharesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ export function convertToShares(

return (assets * totalSupply) / totalAssets
}

export function convertToAssets(
totalAssets: bigint,
totalSupply: bigint,
shares: bigint,
): bigint {
if (totalSupply === BigInt(0)) {
return shares
}

return (shares * totalAssets) / totalSupply
}
Loading

0 comments on commit 6279a68

Please sign in to comment.