Skip to content

Commit

Permalink
Automatic Redemption updates
Browse files Browse the repository at this point in the history
  • Loading branch information
mliu committed Nov 2, 2024
1 parent b6b93b1 commit 5e80e7d
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 2 deletions.
38 changes: 38 additions & 0 deletions packages/huma-widget/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ To be used when re-enabling autopay and other pool actions that require allowanc
<dd><p>Lend pool supply widget for Solana pools</p></dd>
<dt><a href="#SolanaPaymentWidget">SolanaPaymentWidget(props)</a></dt>
<dd><p>Lend pool supply widget for Solana pools</p></dd>
<dt><a href="#SolanaEnableAutoRedemptionWidget">SolanaEnableAutoRedemptionWidget(props)</a></dt>
<dd><p>Lend pool supply widget for Solana pools</p></dd>
</dl>

## Typedefs
Expand Down Expand Up @@ -83,6 +85,8 @@ To be used when re-enabling autopay and other pool actions that require allowanc
<dd><p>Lend pool supply props</p></dd>
<dt><a href="#SolanaLendCancelRedemptionProps">SolanaLendCancelRedemptionProps</a> : <code>Object</code></dt>
<dd><p>Lend pool supply props</p></dd>
<dt><a href="#SolanaEnableAutoRedemptionProps">SolanaEnableAutoRedemptionProps</a> : <code>Object</code></dt>
<dd><p>Lend pool supply props</p></dd>
<dt><a href="#SolanaLendSupplyProps">SolanaLendSupplyProps</a> : <code>Object</code></dt>
<dd><p>Lend pool supply props</p></dd>
<dt><a href="#LendSupplyProps">LendSupplyProps</a> : <code>Object</code></dt>
Expand Down Expand Up @@ -147,6 +151,8 @@ To be used when re-enabling autopay and other pool actions that require allowanc
<dd><p>Lend pool supply widget props for Solana pools</p></dd>
<dt><a href="#SolanaPaymentWidgetProps">SolanaPaymentWidgetProps</a> : <code>Object</code></dt>
<dd><p>Lend pool supply widget props for Solana pools</p></dd>
<dt><a href="#SolanaEnableAutoRedemptionWidgetProps">SolanaEnableAutoRedemptionWidgetProps</a> : <code>Object</code></dt>
<dd><p>Lend pool supply widget props for Solana pools</p></dd>
</dl>

<a name="InvoiceFactoringBorrowWidget"></a>
Expand Down Expand Up @@ -421,6 +427,17 @@ To be used when re-enabling autopay and other pool actions that require allowanc
| --- | --- | --- |
| props | [<code>SolanaPaymentWidgetProps</code>](#SolanaPaymentWidgetProps) | <p>Widget props</p> |

<a name="SolanaEnableAutoRedemptionWidget"></a>

## SolanaEnableAutoRedemptionWidget(props)
<p>Lend pool supply widget for Solana pools</p>

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| props | [<code>SolanaEnableAutoRedemptionWidgetProps</code>](#SolanaEnableAutoRedemptionWidgetProps) | <p>Widget props</p> |

<a name="CreditLineApproveProps"></a>

## CreditLineApproveProps : <code>Object</code>
Expand Down Expand Up @@ -641,6 +658,21 @@ To be used when re-enabling autopay and other pool actions that require allowanc
| poolState | <code>SolanaPoolState</code> | <p>The current state config of the pool. * @property {function():void} handleClose Function to notify to close the widget modal when user clicks the 'x' close button.</p> |
| handleSuccess | <code>function</code> | <p>Optional function to notify that the lending pool supply action is successful.</p> |

<a name="SolanaEnableAutoRedemptionProps"></a>

## SolanaEnableAutoRedemptionProps : <code>Object</code>
<p>Lend pool supply props</p>

**Kind**: global typedef
**Properties**

| Name | Type | Description |
| --- | --- | --- |
| poolInfo | <code>SolanaPoolInfo</code> | <p>The metadata of the pool.</p> |
| poolState | <code>SolanaPoolState</code> | <p>The current state config of the pool.</p> |
| handleClose | <code>function</code> | <p>Function to notify to close the widget modal when user clicks the 'x' close button.</p> |
| handleSuccess | <code>function</code> | <p>Optional function to notify that the lending pool supply action is successful.</p> |

<a name="SolanaLendSupplyProps"></a>

## SolanaLendSupplyProps : <code>Object</code>
Expand Down Expand Up @@ -1031,3 +1063,9 @@ To be used when re-enabling autopay and other pool actions that require allowanc
<p>Lend pool supply widget props for Solana pools</p>

**Kind**: global typedef
<a name="SolanaEnableAutoRedemptionWidgetProps"></a>

## SolanaEnableAutoRedemptionWidgetProps : <code>Object</code>
<p>Lend pool supply widget props for Solana pools</p>

**Kind**: global typedef
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { timeUtil } from '@huma-finance/shared'
import { SolanaPoolState } from '@huma-finance/web-shared'
import dayjs from 'dayjs'
import React, { useCallback } from 'react'
import { Box, css, useTheme } from '@mui/material'
import { useAppDispatch } from '../../../hooks/useRedux'
import { WrapperModal } from '../../WrapperModal'
import { WIDGET_STEP } from '../../../store/widgets.store'
import { setStep } from '../../../store/widgets.reducers'
import { BottomButton } from '../../BottomButton'
import { AutoPaybackImg } from '../../images'

type Props = {
poolState: SolanaPoolState
}

export function ApproveAllowance({ poolState }: Props): React.ReactElement {
const theme = useTheme()
const dispatch = useAppDispatch()
const handleNext = useCallback(() => {
dispatch(setStep(WIDGET_STEP.Transfer))
}, [dispatch])

const styles = {
iconWrapper: css`
${theme.cssMixins.rowCentered};
margin-top: ${theme.spacing(6)};
& > img {
width: 220px;
}
`,
description: css`
margin-top: ${theme.spacing(4)};
font-weight: 400;
font-size: 16px;
color: ${theme.palette.text.secondary};
padding: ${theme.spacing(0, 1)};
`,
}

const lockupEndTime = dayjs()
.add(poolState.withdrawalLockupPeriodDays ?? 0, 'day')
.date(1)
const withdrawTime = lockupEndTime.add(1, 'month')

return (
<WrapperModal title='Auto-Redemption'>
<Box css={styles.iconWrapper}>
<img src={AutoPaybackImg} alt='auto-payback' />
</Box>
<Box css={styles.description}>
This allowance transaction will enable auto-redemption for your existing
tranche shares. Redemption requests will be automatically submitted on{' '}
{timeUtil.timestampToLL(lockupEndTime.unix())}. Your deposit can be
redeemed and yield rewards will stop on{' '}
{timeUtil.timestampToLL(withdrawTime.unix())}.
</Box>
<BottomButton variant='contained' onClick={handleNext}>
APPROVE ALLOWANCE
</BottomButton>
</WrapperModal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {
convertToShares,
getSentinelAddress,
getTokenAccounts,
SolanaPoolInfo,
} from '@huma-finance/shared'
import React, { useCallback, useEffect, useState } from 'react'

import {
SolanaPoolState,
useHumaProgram,
useLenderAccounts,
useTrancheTokenAccounts,
} from '@huma-finance/web-shared'
import {
createApproveCheckedInstruction,
TOKEN_2022_PROGRAM_ID,
} 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 } from '../../../hooks/useRedux'
import { setError, setStep } from '../../../store/widgets.reducers'
import { WIDGET_STEP } from '../../../store/widgets.store'
import { LoadingModal } from '../../LoadingModal'
import { SolanaTxSendModal } from '../../SolanaTxSendModal'

type Props = {
poolInfo: SolanaPoolInfo
poolState: SolanaPoolState
}

export function Transfer({
poolInfo,
poolState,
}: Props): React.ReactElement | null {
const dispatch = useAppDispatch()
const { publicKey } = useWallet()
const sentinel = getSentinelAddress(poolInfo.chainId)
const [transaction, setTransaction] = useState<Transaction>()
const {
juniorLenderApprovedAccountPDA,
seniorLenderApprovedAccountPDA,
seniorLenderStateAccount,
juniorLenderStateAccount,
seniorTrancheMintSupply,
juniorTrancheMintSupply,
loading: isLoadingLenderAccounts,
} = useLenderAccounts(poolInfo.chainId, poolInfo.poolName)
const {
seniorTokenAccount,
juniorTokenAccount,
loading: isLoadingTrancheTokenAccounts,
} = useTrancheTokenAccounts(poolInfo)
const program = useHumaProgram(poolInfo.chainId)

const handleSuccess = useCallback(() => {
dispatch(setStep(WIDGET_STEP.Done))
}, [dispatch])

useEffect(() => {
async function getTx() {
if (
!publicKey ||
transaction ||
isLoadingLenderAccounts ||
isLoadingTrancheTokenAccounts
) {
return
}

const tx = new Transaction()

const { seniorTrancheATA, juniorTrancheATA } = getTokenAccounts(
poolInfo,
publicKey,
)
const sentinelPubKey = new PublicKey(sentinel)

if (!seniorTokenAccount?.amount && !juniorTokenAccount?.amount) {
dispatch(
setError({ errorMessage: 'Error reading tranche token balance' }),
)
return
}
if (
seniorTokenAccount &&
seniorTokenAccount.amount > 0 &&
(seniorTokenAccount.delegate == null ||
!sentinelPubKey.equals(seniorTokenAccount.delegate) ||
seniorTokenAccount.delegatedAmount < seniorTokenAccount.amount)
) {
const sharesAmount = convertToShares(
new BN(poolState.seniorTrancheAssets ?? 0),
seniorTrancheMintSupply ?? new BN(0),
new BN(seniorTokenAccount.amount.toString()),
)
tx.add(
createApproveCheckedInstruction(
seniorTrancheATA,
new PublicKey(poolInfo.seniorTrancheMint),
new PublicKey(sentinel), // delegate
publicKey, // owner of the wallet
BigInt(sharesAmount.muln(1.1).toString()), // amount
poolInfo.trancheDecimals,
undefined, // multiSigners
TOKEN_2022_PROGRAM_ID,
),
)
}
if (
juniorTokenAccount &&
juniorTokenAccount.amount > 0 &&
(juniorTokenAccount.delegate == null ||
!sentinelPubKey.equals(juniorTokenAccount.delegate) ||
juniorTokenAccount.delegatedAmount < juniorTokenAccount.amount)
) {
const sharesAmount = convertToShares(
new BN(poolState.juniorTrancheAssets ?? 0),
juniorTrancheMintSupply ?? new BN(0),
new BN(juniorTokenAccount.amount.toString()),
)
tx.add(
createApproveCheckedInstruction(
juniorTrancheATA,
new PublicKey(poolInfo.juniorTrancheMint),
new PublicKey(sentinel), // delegate
publicKey, // owner of the wallet
BigInt(sharesAmount.muln(1.1).toString()), // amount
poolInfo.trancheDecimals,
undefined, // multiSigners
TOKEN_2022_PROGRAM_ID,
),
)
}
if (!tx.instructions.length) {
dispatch(
setError({ errorMessage: 'No tranches require Auto-Redemption' }),
)
return
}

setTransaction(tx)
}
getTx()
}, [
dispatch,
isLoadingLenderAccounts,
isLoadingTrancheTokenAccounts,
juniorLenderApprovedAccountPDA,
juniorLenderStateAccount,
juniorTokenAccount,
juniorTrancheMintSupply,
poolInfo,
poolState.juniorTrancheAssets,
poolState.seniorTrancheAssets,
program.methods,
publicKey,
seniorLenderApprovedAccountPDA,
seniorLenderStateAccount,
seniorTokenAccount,
seniorTrancheMintSupply,
sentinel,
transaction,
])

if (isLoadingLenderAccounts || isLoadingTrancheTokenAccounts) {
return <LoadingModal title='Auto-Redeem' />
}

return (
<SolanaTxSendModal
tx={transaction}
chainId={poolInfo.chainId}
handleSuccess={handleSuccess}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
CloseModalOptions,
SolanaPoolInfo,
timeUtil,
} from '@huma-finance/shared'
import { SolanaPoolState } from '@huma-finance/web-shared'
import dayjs from 'dayjs'
import React from 'react'
import { useAppSelector } from '../../../hooks/useRedux'
import { selectWidgetState } from '../../../store/widgets.selectors'
import { SolanaTxDoneModal } from '../../SolanaTxDoneModal'

type Props = {
poolInfo: SolanaPoolInfo
poolState: SolanaPoolState
handleAction: (options?: CloseModalOptions) => void
}

export function Success({
poolInfo,
poolState,
handleAction,
}: Props): React.ReactElement {
const { solanaSignature } = useAppSelector(selectWidgetState)

const lockupEndTime = dayjs()
.add(poolState.withdrawalLockupPeriodDays ?? 0, 'day')
.date(1)
const withdrawTime = lockupEndTime.add(1, 'month')
const content = [
`Redemption request will be automatically submitted on ${timeUtil.timestampToLL(
lockupEndTime.unix(),
)}. Your deposit can be redeemed and yield rewards will stop on ${timeUtil.timestampToLL(
withdrawTime.unix(),
)}.`,
]

return (
<SolanaTxDoneModal
handleAction={handleAction}
content={content}
chainId={poolInfo.chainId}
solanaSignature={solanaSignature}
buttonText='DONE'
/>
)
}
Loading

0 comments on commit 5e80e7d

Please sign in to comment.