diff --git a/packages/huma-widget/API.md b/packages/huma-widget/API.md index 963d5d6..8217748 100644 --- a/packages/huma-widget/API.md +++ b/packages/huma-widget/API.md @@ -3,6 +3,8 @@
StellarLendAddRedemption()

Stellar add redemption props – request to withdraw (redeem) shares from a tranche.

+
StellarLendWithdraw()
+

Stellar lend pool withdraw props – disburse (pool open) or withdraw after pool closure.

InvoiceFactoringBorrowWidget(props)

Invoice factoring borrow widget

InvoiceFactoringPaymentWidget(props)
@@ -56,6 +58,8 @@ To be used when re-enabling autopay and other pool actions that require allowanc

Lend pool supply widget for Stellar pools

StellarLendAddRedemptionWidget(props)

Lend pool add redemption widget for Stellar pools

+
StellarLendWithdrawWidget(props)
+

Lend pool withdraw widget for Stellar pools (disburse when pool open, withdraw_after_pool_closure when closed)

StellarBorrowWidget(props)

Borrow widget for Stellar pools

SolanaEnableAutoRedemptionWidget(props)
@@ -171,6 +175,8 @@ To be used when re-enabling autopay and other pool actions that require allowanc

Lend pool supply widget props for Stellar pools

StellarLendAddRedemptionWidgetProps : Object

Lend pool add redemption widget props for Stellar pools

+
StellarLendWithdrawWidgetProps : Object
+

Lend pool withdraw widget props for Stellar pools

StellarLendSupplyWidgetProps : Object

Borrow widget props for Stellar pools

SolanaEnableAutoRedemptionWidgetProps : Object
@@ -193,6 +199,22 @@ To be used when re-enabling autopay and other pool actions that require allowanc | handleClose | function |

Function to close the widget modal.

| | handleSuccess | function |

Optional callback when redemption request succeeds.

| + + +## StellarLendWithdraw() +

Stellar lend pool withdraw props – disburse (pool open) or withdraw after pool closure.

+ +**Kind**: global function +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| poolInfo | StellarPoolInfo |

The metadata of the pool.

| +| poolState | StellarPoolState |

The current state config of the pool.

| +| tranche | TrancheType |

The tranche to withdraw from (junior or senior).

| +| handleClose | function |

Function to close the widget modal.

| +| handleSuccess | function |

Optional callback when withdraw succeeds.

| + ## InvoiceFactoringBorrowWidget(props) ⇒ @@ -499,6 +521,17 @@ To be used when re-enabling autopay and other pool actions that require allowanc | --- | --- | --- | | props | [StellarLendAddRedemptionWidgetProps](#StellarLendAddRedemptionWidgetProps) |

Widget props

| + + +## StellarLendWithdrawWidget(props) +

Lend pool withdraw widget for Stellar pools (disburse when pool open, withdraw_after_pool_closure when closed)

+ +**Kind**: global function + +| Param | Type | Description | +| --- | --- | --- | +| props | [StellarLendWithdrawWidgetProps](#StellarLendWithdrawWidgetProps) |

Widget props

| + ## StellarBorrowWidget(props) @@ -1213,6 +1246,12 @@ To be used when re-enabling autopay and other pool actions that require allowanc ## StellarLendAddRedemptionWidgetProps : Object

Lend pool add redemption widget props for Stellar pools

+**Kind**: global typedef + + +## StellarLendWithdrawWidgetProps : Object +

Lend pool withdraw widget props for Stellar pools

+ **Kind**: global typedef diff --git a/packages/huma-widget/src/components/Lend/stellarWithdraw/1-Confirm.tsx b/packages/huma-widget/src/components/Lend/stellarWithdraw/1-Confirm.tsx new file mode 100644 index 0000000..e49ea5b --- /dev/null +++ b/packages/huma-widget/src/components/Lend/stellarWithdraw/1-Confirm.tsx @@ -0,0 +1,60 @@ +import { StellarPoolInfo } from '@huma-finance/shared' +import { Box, css, useTheme } from '@mui/material' +import React from 'react' +import { BottomButton } from '../../BottomButton' +import { WrapperModal } from '../../WrapperModal' + +type Props = { + poolInfo: StellarPoolInfo + withdrawableAmountFormatted: string + onConfirm: () => void +} + +export function Confirm({ + poolInfo, + withdrawableAmountFormatted, + onConfirm, +}: Props): React.ReactElement { + const theme = useTheme() + const { symbol } = poolInfo.underlyingToken + + const styles = { + itemWrapper: css` + margin-top: ${theme.spacing(9)}; + color: ${theme.palette.text.primary}; + font-weight: 400; + font-size: 16px; + line-height: 175%; + letter-spacing: 0.15px; + `, + item: css` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: ${theme.spacing(3)}; + `, + itemValue: css` + font-size: 20px; + font-weight: 700; + `, + } + + return ( + + + + Available to withdraw + + {withdrawableAmountFormatted} {symbol} + + + + + WITHDRAW + + + ) +} diff --git a/packages/huma-widget/src/components/Lend/stellarWithdraw/2-Transfer.tsx b/packages/huma-widget/src/components/Lend/stellarWithdraw/2-Transfer.tsx new file mode 100644 index 0000000..8762c2e --- /dev/null +++ b/packages/huma-widget/src/components/Lend/stellarWithdraw/2-Transfer.tsx @@ -0,0 +1,67 @@ +import { + STELLAR_CHAINS_INFO, + StellarPoolInfo, + TrancheType, +} from '@huma-finance/shared' +import { Client as TrancheVaultClient } from '@huma-finance/soroban-tranche-vault' +import React, { useCallback, useContext, useEffect, useState } from 'react' + +import { + getClientCommonParams, + StellarConnectionContext, +} from '@huma-finance/web-shared' +import { useAppDispatch } from '../../../hooks/useRedux' +import { setStep } from '../../../store/widgets.reducers' +import { WIDGET_STEP } from '../../../store/widgets.store' +import { StellarTxSendModal } from '../../StellarTxSendModal' + +type Props = { + poolInfo: StellarPoolInfo + tranche: TrancheType + poolIsClosed: boolean +} + +export function Transfer({ + poolInfo, + tranche, + poolIsClosed, +}: Props): React.ReactElement | null { + const dispatch = useAppDispatch() + const { address: stellarAddress } = useContext(StellarConnectionContext) + const [tx, setTx] = useState + > | null>(null) + + const handleSuccess = useCallback(async () => { + dispatch(setStep(WIDGET_STEP.Done)) + }, [dispatch]) + + useEffect(() => { + async function getTx() { + if (!stellarAddress) { + return + } + + const chainMetadata = STELLAR_CHAINS_INFO[poolInfo.chainId] + const contractId = + tranche === 'senior' ? poolInfo.seniorTranche! : poolInfo.juniorTranche + const trancheVaultClient = new TrancheVaultClient({ + publicKey: stellarAddress, + contractId, + ...getClientCommonParams(chainMetadata, stellarAddress), + }) + + const assembledTx = poolIsClosed + ? await trancheVaultClient.withdraw_after_pool_closure({ + lender: stellarAddress, + }) + : await trancheVaultClient.disburse({ + lender: stellarAddress, + }) + setTx(assembledTx) + } + getTx() + }, [poolInfo, tranche, poolIsClosed, stellarAddress]) + + return +} diff --git a/packages/huma-widget/src/components/Lend/stellarWithdraw/3-Done.tsx b/packages/huma-widget/src/components/Lend/stellarWithdraw/3-Done.tsx new file mode 100644 index 0000000..5322bcd --- /dev/null +++ b/packages/huma-widget/src/components/Lend/stellarWithdraw/3-Done.tsx @@ -0,0 +1,38 @@ +import { formatNumber, StellarPoolInfo } from '@huma-finance/shared' +import React, { useCallback } from 'react' +import { useAppSelector } from '../../../hooks/useRedux' +import { selectWidgetState } from '../../../store/widgets.selectors' +import { StellarTxDoneModal } from '../../StellarTxDoneModal' + +type Props = { + poolInfo: StellarPoolInfo + withdrawAmount?: number + handleAction: (options?: { isSuccess?: boolean }) => void +} + +export function Done({ + poolInfo, + withdrawAmount, + handleAction, +}: Props): React.ReactElement { + const { txHash } = useAppSelector(selectWidgetState) + const { symbol } = poolInfo.underlyingToken + + const content = [ + `${formatNumber(withdrawAmount ?? 0)} ${symbol} withdrawn to your wallet`, + ] + + const handleUserAction = useCallback(() => { + handleAction({ isSuccess: true }) + }, [handleAction]) + + return ( + + ) +} diff --git a/packages/huma-widget/src/components/Lend/stellarWithdraw/index.tsx b/packages/huma-widget/src/components/Lend/stellarWithdraw/index.tsx new file mode 100644 index 0000000..792358e --- /dev/null +++ b/packages/huma-widget/src/components/Lend/stellarWithdraw/index.tsx @@ -0,0 +1,170 @@ +import { + CloseModalOptions, + formatNumberFixed, + STELLAR_CHAINS_INFO, + StellarPoolInfo, + tokenDecimalUtils, + TrancheType, +} from '@huma-finance/shared' +import { Client as TrancheVaultClient } from '@huma-finance/soroban-tranche-vault' +import { + StellarConnectionContext, + StellarPoolState, +} from '@huma-finance/web-shared' +import React, { useCallback, useContext, useEffect, useState } from 'react' +import { useDispatch } from 'react-redux' + +import { useAppSelector } from '../../../hooks/useRedux' +import { setStep, setWithdrawAmount } from '../../../store/widgets.reducers' +import { selectWidgetState } from '../../../store/widgets.selectors' +import { WIDGET_STEP } from '../../../store/widgets.store' +import { ErrorModal } from '../../ErrorModal' +import { WidgetWrapper } from '../../WidgetWrapper' +import { Confirm } from './1-Confirm' +import { Transfer } from './2-Transfer' +import { Done } from './3-Done' + +/** + * Stellar lend pool withdraw props – disburse (pool open) or withdraw after pool closure. + * @property {StellarPoolInfo} poolInfo The metadata of the pool. + * @property {StellarPoolState} poolState The current state config of the pool. + * @property {TrancheType} tranche The tranche to withdraw from (junior or senior). + * @property {function((CloseModalOptions|undefined)):void} handleClose Function to close the widget modal. + * @property {function():void|undefined} handleSuccess Optional callback when withdraw succeeds. + */ +export interface StellarLendWithdrawProps { + poolInfo: StellarPoolInfo + poolState: StellarPoolState + tranche: TrancheType + handleClose: (options?: CloseModalOptions) => void + handleSuccess?: () => void +} + +export function StellarLendWithdraw({ + poolInfo, + poolState, + tranche, + handleClose, + handleSuccess, +}: StellarLendWithdrawProps): React.ReactElement | null { + const dispatch = useDispatch() + const { address: stellarAddress } = useContext(StellarConnectionContext) + const { step, errorMessage, withdrawAmount } = + useAppSelector(selectWidgetState) + const [withdrawableAmount, setWithdrawableAmount] = useState( + BigInt(0), + ) + const [isLoadingWithdrawable, setIsLoadingWithdrawable] = useState(true) + + const { symbol, decimals } = poolInfo.underlyingToken + const title = `Withdraw ${symbol}` + const poolIsClosed = poolState?.status === 'closed' + const withdrawableAmountFormatted = formatNumberFixed( + tokenDecimalUtils.formatUnits(withdrawableAmount, decimals), + 2, + ) + + useEffect(() => { + if (!step && stellarAddress) { + dispatch(setStep(WIDGET_STEP.ConfirmWithdrawOnly)) + } + }, [dispatch, step, stellarAddress]) + + useEffect(() => { + async function fetchWithdrawable() { + if (!stellarAddress || !poolInfo) { + setIsLoadingWithdrawable(false) + return + } + setIsLoadingWithdrawable(true) + try { + const chainMetadata = STELLAR_CHAINS_INFO[poolInfo.chainId] + const contractId = + tranche === 'senior' + ? poolInfo.seniorTranche! + : poolInfo.juniorTranche + const client = new TrancheVaultClient({ + publicKey: stellarAddress, + contractId, + networkPassphrase: chainMetadata.networkPassphrase, + rpcUrl: chainMetadata.rpc, + }) + const res = await client.get_latest_redemption_record({ + lender: stellarAddress, + }) + const record = res.result + const withdrawable = + BigInt(record.total_amount_processed) - + BigInt(record.total_amount_withdrawn) + setWithdrawableAmount(withdrawable) + } catch (err) { + console.error('Failed to fetch withdrawable amount:', err) + setWithdrawableAmount(BigInt(0)) + } finally { + setIsLoadingWithdrawable(false) + } + } + fetchWithdrawable() + }, [stellarAddress, poolInfo, tranche]) + + const handleConfirmWithdraw = useCallback(() => { + dispatch(setWithdrawAmount(Number(withdrawableAmountFormatted))) + dispatch(setStep(WIDGET_STEP.Transfer)) + }, [dispatch, withdrawableAmountFormatted]) + + const handleWithdrawSuccess = useCallback(() => { + handleSuccess?.() + }, [handleSuccess]) + + if (isLoadingWithdrawable && step === WIDGET_STEP.ConfirmWithdrawOnly) { + return ( + + ) + } + + return ( + + {step === WIDGET_STEP.ConfirmWithdrawOnly && ( + + )} + {step === WIDGET_STEP.Transfer && ( + + )} + {step === WIDGET_STEP.Done && ( + + )} + {step === WIDGET_STEP.Error && ( + + )} + + ) +} diff --git a/packages/huma-widget/src/index.tsx b/packages/huma-widget/src/index.tsx index 1d0ced2..b96ab97 100644 --- a/packages/huma-widget/src/index.tsx +++ b/packages/huma-widget/src/index.tsx @@ -85,6 +85,10 @@ import { StellarLendSupply, StellarLendSupplyProps, } from './components/Lend/stellarSupply' +import { + StellarLendWithdraw, + StellarLendWithdrawProps, +} from './components/Lend/stellarWithdraw' import { LendSupply, LendSupplyProps } from './components/Lend/supply' import { LendSupplyPropsV2, LendSupplyV2 } from './components/Lend/supplyV2' import { LendWithdraw, LendWithdrawProps } from './components/Lend/withdraw' @@ -739,6 +743,28 @@ export function StellarLendAddRedemptionWidget( ) } +/** + * Lend pool withdraw widget props for Stellar pools + * @typedef {Object} StellarLendWithdrawWidgetProps + */ +type StellarLendWithdrawWidgetProps = StellarLendWithdrawProps & + GenericWidgetProps + +/** + * Lend pool withdraw widget for Stellar pools (disburse when pool open, withdraw_after_pool_closure when closed) + * + * @param {StellarLendWithdrawWidgetProps} props - Widget props + */ +export function StellarLendWithdrawWidget( + props: StellarLendWithdrawWidgetProps, +) { + return ( + + + + ) +} + /** * Borrow widget props for Stellar pools * @typedef {Object} StellarLendSupplyWidgetProps