Skip to content

Commit

Permalink
Lend stats card
Browse files Browse the repository at this point in the history
  • Loading branch information
mliu committed Nov 8, 2024
1 parent 37453c3 commit 105bf18
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 53 deletions.
18 changes: 8 additions & 10 deletions packages/huma-shared/src/solana/utils/tokenAssetsSharesUtils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { BN } from '@coral-xyz/anchor'

export function convertToShares(
totalAssets: BN,
totalSupply: BN,
assets: BN,
): BN {
if (!totalSupply.isZero() && totalAssets.isZero()) {
return new BN(0)
totalAssets: bigint,
totalSupply: bigint,
assets: bigint,
): bigint {
if (totalSupply !== BigInt(0) && totalAssets === BigInt(0)) {
return BigInt(0)
}
if (totalSupply.isZero()) {
if (totalSupply === BigInt(0)) {
return assets
}

return assets.mul(totalSupply).div(totalAssets)
return (assets * totalSupply) / totalAssets
}
1 change: 1 addition & 0 deletions packages/huma-shared/src/stellar/metadata/testnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const STELLAR_TESTNET_METADATA: StellarPoolsInfo = {
creditManager: 'CBEH5SKVKC6GXP5FQLAUFX43GAFRXZDOHDUQW3CRFD5BQVH7L6YSBP4V',
creditStorage: 'CADDOLDFYN6Y2DXNYMX2ILVLPLU5W7MAQ7GBOOYWJ6JCH4DGHLUH2FB3',
juniorTranche: 'CB6K4IUC3CJHIWVHHLBDTGXVS6CT64EKGD5CGBIDNMSAKVZHCWQ3LM2D',
trancheDecimals: 6,
underlyingToken: {
address: 'CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA',
symbol: 'USDC',
Expand Down
1 change: 1 addition & 0 deletions packages/huma-shared/src/stellar/types/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type StellarPoolInfo = {
creditStorage: string
juniorTranche: string
seniorTranche?: string
trancheDecimals: number
underlyingToken: {
address: string
symbol: string
Expand Down
8 changes: 4 additions & 4 deletions packages/huma-web-shared/src/stellar/StellarWeb3Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { requestAccess, WatchWalletChanges } from '@stellar/freighter-api'
import React, { useState, createContext, useMemo, useEffect } from 'react'

export interface StellarConnectionContextType {
address: string
address: string | null
requestAccessFn: () => void
}

export const StellarConnectionContext =
createContext<StellarConnectionContextType>({
address: '',
address: null,
requestAccessFn: () => {},
})

Expand All @@ -19,7 +19,7 @@ type WCProps<P = {}> = P & {
}

export function StellarWeb3Provider({ children }: WCProps) {
const [address, setAddress] = useState('')
const [address, setAddress] = useState<string | null>(null)

// Watch for changes in the Freighter wallet address on a 3 second interval by default.
// Need this to catch users switching wallet addresses in the extension and for auto-connecting
Expand All @@ -42,7 +42,7 @@ export function StellarWeb3Provider({ children }: WCProps) {
const accessObj = await requestAccess()

if (accessObj.error) {
setAddress('')
setAddress(null)
} else {
setAddress(accessObj.address)
}
Expand Down
113 changes: 88 additions & 25 deletions packages/huma-web-shared/src/stellar/hooks/useStellarLender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,55 @@ import {
LenderRedemptionRecord,
} from '@huma-finance/soroban-tranche-vault'
import { useEffect, useState } from 'react'
import { useForceRefresh } from '../../hooks'
import { DepositRecord, fetchStellarDepositRecord } from '../utils'

export const useStellarLender = (
stellarAddress: string,
stellarAddress: string | null,
poolInfo: StellarPoolInfo,
): {
isLoadingStellarLender: boolean
isJuniorApprovedLender: boolean
juniorRedemptionRecord: LenderRedemptionRecord | null
juniorDepositRecord: DepositRecord | null
juniorTrancheWithdrawable: bigint
juniorTrancheShares: bigint
isSeniorApprovedLender: boolean
seniorRedemptionRecord: LenderRedemptionRecord | null
seniorDepositRecord: DepositRecord | null
seniorTrancheWithdrawable: bigint
seniorTrancheShares: bigint
refresh: () => void
} => {
const [isLoadingStellarLender, setIsLoadingStellarLender] = useState(true)
const [isJuniorApprovedLender, setIsJuniorApprovedLender] = useState(false)
const [juniorRedemptionRecord, setJuniorRedemptionRecord] =
useState<LenderRedemptionRecord | null>(null)
const [juniorDepositRecord, setJuniorDepositRecord] =
useState<DepositRecord | null>(null)
const [juniorTrancheWithdrawable, setJuniorTrancheWithdrawable] =
useState<bigint>(BigInt(0))
const [juniorTrancheShares, setJuniorTrancheShares] = useState<bigint>(
BigInt(0),
)
const [isSeniorApprovedLender, setIsSeniorApprovedLender] = useState(false)
const [seniorRedemptionRecord, setSeniorRedemptionRecord] =
useState<LenderRedemptionRecord | null>(null)
const [seniorDepositRecord, setSeniorDepositRecord] =
useState<DepositRecord | null>(null)
const [seniorTrancheWithdrawable, setSeniorTrancheWithdrawable] =
useState<bigint>(BigInt(0))
const [seniorTrancheShares, setSeniorTrancheShares] = useState<bigint>(
BigInt(0),
)
const [refreshCount, refresh] = useForceRefresh()

useEffect(() => {
const fetchData = async () => {
if (!stellarAddress) {
return
}

setIsLoadingStellarLender(true)
try {
const chainMetadata = STELLAR_CHAINS_INFO[poolInfo.chainId]
Expand All @@ -34,17 +62,33 @@ export const useStellarLender = (
contractId: poolInfo.juniorTranche,
rpcUrl: chainMetadata.rpc,
})
const [isJuniorApproveLenderRes, juniorRedemptionRecordRes] =
await Promise.all([
juniorTrancheVaultClient.is_approved_lender({
account: stellarAddress,
}),
juniorTrancheVaultClient.get_latest_redemption_record({
lender: stellarAddress,
}),
])
const [
isJuniorApproveLenderRes,
juniorRedemptionRecordRes,
juniorDepositRecordRes,
juniorTrancheSharesRes,
] = await Promise.all([
juniorTrancheVaultClient.is_approved_lender({
account: stellarAddress,
}),
juniorTrancheVaultClient.get_latest_redemption_record({
lender: stellarAddress,
}),
fetchStellarDepositRecord(poolInfo, 'junior', stellarAddress),
juniorTrancheVaultClient.balance({ id: stellarAddress }),
])
const isJuniorApprovedLenderVal = isJuniorApproveLenderRes.result
const juniorRedemptionRecordVal = juniorRedemptionRecordRes.result
setIsJuniorApprovedLender(isJuniorApprovedLenderVal)
setJuniorRedemptionRecord(juniorRedemptionRecordVal)
setJuniorDepositRecord(juniorDepositRecordRes)
setJuniorTrancheWithdrawable(
BigInt(
juniorRedemptionRecordVal.total_amount_processed -
juniorRedemptionRecordVal.total_amount_withdrawn,
),
)
setJuniorTrancheShares(juniorTrancheSharesRes.result)

let isSeniorApprovedLenderVal = false
let seniorRedemptionRecordVal = null
Expand All @@ -55,23 +99,35 @@ export const useStellarLender = (
contractId: poolInfo.seniorTranche,
rpcUrl: chainMetadata.rpc,
})
const [isSeniorApproveLenderRes, seniorRedemptionRecordRes] =
await Promise.all([
seniorTrancheVaultClient.is_approved_lender({
account: stellarAddress,
}),
seniorTrancheVaultClient.get_latest_redemption_record({
lender: stellarAddress,
}),
])
const [
isSeniorApproveLenderRes,
seniorRedemptionRecordRes,
seniorDepositRecordRes,
seniorTrancheSharesRes,
] = await Promise.all([
seniorTrancheVaultClient.is_approved_lender({
account: stellarAddress,
}),
seniorTrancheVaultClient.get_latest_redemption_record({
lender: stellarAddress,
}),
fetchStellarDepositRecord(poolInfo, 'senior', stellarAddress),
seniorTrancheVaultClient.balance({ id: stellarAddress }),
])
isSeniorApprovedLenderVal = isSeniorApproveLenderRes.result
seniorRedemptionRecordVal = seniorRedemptionRecordRes.result
}

setIsJuniorApprovedLender(isJuniorApprovedLenderVal)
setJuniorRedemptionRecord(juniorRedemptionRecordVal)
setIsSeniorApprovedLender(isSeniorApprovedLenderVal)
setSeniorRedemptionRecord(seniorRedemptionRecordVal)
setSeniorDepositRecord(seniorDepositRecordRes)
setIsSeniorApprovedLender(isSeniorApprovedLenderVal)
setSeniorRedemptionRecord(seniorRedemptionRecordVal)
setSeniorTrancheWithdrawable(
BigInt(
seniorRedemptionRecordVal.total_amount_processed -
seniorRedemptionRecordVal.total_amount_withdrawn,
),
)
setSeniorTrancheShares(seniorTrancheSharesRes.result)
}
} catch (error) {
console.error('Error fetching Stellar lender data:', error)
} finally {
Expand All @@ -80,13 +136,20 @@ export const useStellarLender = (
}

fetchData()
}, [stellarAddress, poolInfo])
}, [stellarAddress, poolInfo, refreshCount])

return {
isLoadingStellarLender,
isJuniorApprovedLender,
juniorRedemptionRecord,
juniorDepositRecord,
juniorTrancheWithdrawable,
juniorTrancheShares,
isSeniorApprovedLender,
seniorRedemptionRecord,
seniorDepositRecord,
seniorTrancheWithdrawable,
seniorTrancheShares,
refresh,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { fetchStellarTokenBalance } from '../utils'
export const useStellarTokenBalance = (
chainMetadata: StellarChainInfo,
tokenAddress: string,
accountAddress: string,
accountAddress: string | null,
sourceAddress?: string,
): {
tokenBalance: number | null
Expand All @@ -17,6 +17,10 @@ export const useStellarTokenBalance = (

useEffect(() => {
const getTokenBalance = async () => {
if (!accountAddress) {
return
}

setIsLoadingTokenBalance(true)
try {
const fetchedBalance = await fetchStellarTokenBalance(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export type StellarPoolState = {
amountOriginated?: number
amountRepaid?: number
disbursementReserve?: number
juniorTrancheTokenSupply?: string
seniorTrancheTokenSupply?: string
accruedIncomes?: {
eaIncome: string
protocolIncome: string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
STELLAR_CHAINS_INFO,
StellarPoolInfo,
TrancheType,
} from '@huma-finance/shared'
import { SorobanRpc, Address, xdr, scValToNative } from '@stellar/stellar-sdk'

const getDepositRecordKey = (contractId: string, address: string) => {
const addressScVal = new Address(address).toScVal()
return xdr.LedgerKey.contractData(
new xdr.LedgerKeyContractData({
contract: new Address(contractId).toScAddress(),
key: xdr.ScVal.scvVec([
xdr.ScVal.scvSymbol('DepositRecord'),
addressScVal,
]),
durability: xdr.ContractDataDurability.persistent(),
}),
)
}

export type DepositRecord = {
lastDepositTime: number
principal: bigint
}

export async function fetchStellarDepositRecord(
poolInfo: StellarPoolInfo,
tranche: TrancheType,
account: string,
): Promise<DepositRecord> {
try {
const chainMetadata = STELLAR_CHAINS_INFO[poolInfo.chainId]
const server = new SorobanRpc.Server(chainMetadata.rpc)

const key = getDepositRecordKey(
tranche === 'senior' ? poolInfo.seniorTranche! : poolInfo.juniorTranche,
account,
)
// Get the contract data with proper durability
const response = await server.getLedgerEntries(key)

const contractData = response.entries[0].val

if (contractData.switch() === xdr.LedgerEntryType.contractData()) {
const data = scValToNative(contractData.contractData().val())

if (
data.last_deposit_time === undefined ||
data.principal === undefined
) {
throw new Error('Failed to fetch deposit record')
}

return {
lastDepositTime: Number(data.last_deposit_time),
principal: BigInt(data.principal),
}
}

throw new Error('Failed to fetch deposit record')
} catch (error) {
console.error('Error fetching deposit record:', error)
throw error
}
}
1 change: 1 addition & 0 deletions packages/huma-web-shared/src/stellar/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './fetchStellarTokenBalance'
export * from './stellarTryFn'
export * from './getClientCommonParams'
export * from './fetchStellarDepositRecord'
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
} from '@solana/spl-token'
import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey, Transaction } from '@solana/web3.js'
import { BN } from '@coral-xyz/anchor'
import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux'
import { setPointsAccumulated, setStep } from '../../../store/widgets.reducers'
import { selectWidgetState } from '../../../store/widgets.selectors'
Expand Down Expand Up @@ -167,23 +166,23 @@ export function Transfer({
// Approve automatic redemptions
const sharesAmount = convertToShares(
selectedTranche === 'senior'
? new BN(poolState.seniorTrancheAssets ?? 0)
: new BN(poolState.juniorTrancheAssets ?? 0),
? BigInt(poolState.seniorTrancheAssets ?? 0)
: BigInt(poolState.juniorTrancheAssets ?? 0),
selectedTranche === 'senior'
? seniorTrancheMintSupply ?? new BN(0)
: juniorTrancheMintSupply ?? new BN(0),
supplyBigNumber,
? BigInt(seniorTrancheMintSupply?.toString() ?? 0)
: BigInt(juniorTrancheMintSupply?.toString() ?? 0),
BigInt(supplyBigNumber.toString()),
)
const existingShares = convertToShares(
selectedTranche === 'senior'
? new BN(poolState.seniorTrancheAssets ?? 0)
: new BN(poolState.juniorTrancheAssets ?? 0),
? BigInt(poolState.seniorTrancheAssets ?? 0)
: BigInt(poolState.juniorTrancheAssets ?? 0),
selectedTranche === 'senior'
? seniorTrancheMintSupply ?? new BN(0)
: juniorTrancheMintSupply ?? new BN(0),
? BigInt(seniorTrancheMintSupply?.toString() ?? 0)
: BigInt(juniorTrancheMintSupply?.toString() ?? 0),
selectedTranche === 'senior'
? new BN(seniorTokenAccount?.amount.toString() ?? '0')
: new BN(juniorTokenAccount?.amount.toString() ?? '0'),
? BigInt(seniorTokenAccount?.amount.toString() ?? '0')
: BigInt(juniorTokenAccount?.amount.toString() ?? '0'),
)
tx.add(
createApproveCheckedInstruction(
Expand All @@ -195,7 +194,7 @@ export function Transfer({
),
new PublicKey(poolInfo.poolAuthority), // delegate
publicKey, // owner of the wallet
BigInt(sharesAmount.muln(1.1).add(existingShares).toString()), // amount
(sharesAmount * BigInt(11)) / BigInt(10) + existingShares, // amount
poolInfo.trancheDecimals,
undefined, // multiSigners
TOKEN_2022_PROGRAM_ID,
Expand Down

0 comments on commit 105bf18

Please sign in to comment.