@huma-finance/widgets 0.0.62-beta.599 → 0.0.62-beta.612

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/API.md +185 -0
  2. package/dist/cjs/index.cjs +2748 -781
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/index.d.ts +117 -2
  5. package/dist/index.js +2468 -484
  6. package/dist/index.js.map +1 -1
  7. package/package.json +11 -6
  8. package/src/components/CreditLine/solanaBorrow/1-ChooseAmount.tsx +86 -0
  9. package/src/components/CreditLine/solanaBorrow/2-ApproveAllowance.tsx +87 -0
  10. package/src/components/CreditLine/solanaBorrow/index.tsx +96 -0
  11. package/src/components/InputAmountModal.tsx +12 -2
  12. package/src/components/Lend/solanaAddRedemption/1-ChooseTranche.tsx +114 -0
  13. package/src/components/Lend/solanaAddRedemption/2-ChooseAmount.tsx +183 -0
  14. package/src/components/Lend/solanaAddRedemption/3-Transfer.tsx +102 -0
  15. package/src/components/Lend/solanaAddRedemption/4-Done.tsx +29 -0
  16. package/src/components/Lend/solanaAddRedemption/index.tsx +140 -0
  17. package/src/components/Lend/solanaCancelRedemption/1-ChooseTranche.tsx +114 -0
  18. package/src/components/Lend/solanaCancelRedemption/2-Transfer.tsx +99 -0
  19. package/src/components/Lend/solanaCancelRedemption/3-Done.tsx +55 -0
  20. package/src/components/Lend/solanaCancelRedemption/index.tsx +152 -0
  21. package/src/components/Lend/solanaSupply/1-ChooseTranche.tsx +116 -0
  22. package/src/components/Lend/solanaSupply/2-Evaluation.tsx +0 -0
  23. package/src/components/Lend/solanaSupply/3-ChooseAmount.tsx +130 -0
  24. package/src/components/Lend/solanaSupply/4-Transfer.tsx +89 -0
  25. package/src/components/Lend/solanaSupply/5-Success.tsx +50 -0
  26. package/src/components/Lend/solanaSupply/index.tsx +143 -0
  27. package/src/components/SolanaTxDoneModal.tsx +115 -0
  28. package/src/components/SolanaTxSendModal.tsx +61 -0
  29. package/src/components/SolanaViewOnExplorer.tsx +29 -0
  30. package/src/components/WidgetWrapper.tsx +6 -4
  31. package/src/index.tsx +124 -0
  32. package/src/store/widgets.reducers.ts +5 -0
  33. package/src/store/widgets.store.ts +1 -0
@@ -0,0 +1,116 @@
1
+ import { TrancheType, UnderlyingTokenInfo } from '@huma-finance/shared'
2
+ import {
3
+ css,
4
+ FormControl,
5
+ FormControlLabel,
6
+ FormLabel,
7
+ Radio,
8
+ RadioGroup,
9
+ useTheme,
10
+ } from '@mui/material'
11
+ import React from 'react'
12
+
13
+ import { useAppDispatch } from '../../../hooks/useRedux'
14
+ import { setStep } from '../../../store/widgets.reducers'
15
+ import { WIDGET_STEP } from '../../../store/widgets.store'
16
+ import { BottomButton } from '../../BottomButton'
17
+ import { WrapperModal } from '../../WrapperModal'
18
+
19
+ type Props = {
20
+ poolUnderlyingToken: UnderlyingTokenInfo
21
+ selectedTranche: TrancheType | undefined
22
+ changeTranche: (tranche: TrancheType) => void
23
+ }
24
+
25
+ export function ChooseTranche({
26
+ poolUnderlyingToken,
27
+ selectedTranche,
28
+ changeTranche,
29
+ }: Props): React.ReactElement | null {
30
+ const theme = useTheme()
31
+ const dispatch = useAppDispatch()
32
+
33
+ const styles = {
34
+ subTitle: css`
35
+ color: ${theme.palette.text.primary} !important;
36
+ font-weight: 400;
37
+ font-size: 16px;
38
+ line-height: 150%;
39
+ letter-spacing: 0.15px;
40
+ margin-top: ${theme.spacing(5)};
41
+ margin-left: ${theme.spacing(2)};
42
+ `,
43
+ radioGroup: css`
44
+ margin-left: ${theme.spacing(3)};
45
+ margin-top: ${theme.spacing(3)};
46
+ `,
47
+ formControl: css`
48
+ margin-bottom: ${theme.spacing(1)};
49
+
50
+ .MuiFormControlLabel-label {
51
+ color: ${theme.palette.text.primary};
52
+ font-weight: 400;
53
+ font-size: 16px;
54
+ line-height: 150%;
55
+ letter-spacing: 0.15px;
56
+ }
57
+ `,
58
+ }
59
+
60
+ const handleNext = () => {
61
+ dispatch(setStep(WIDGET_STEP.ChooseAmount))
62
+ }
63
+
64
+ const items: {
65
+ label: string
66
+ value: TrancheType
67
+ }[] = [
68
+ {
69
+ label: 'Senior',
70
+ value: 'senior',
71
+ },
72
+ {
73
+ label: 'Junior',
74
+ value: 'junior',
75
+ },
76
+ ]
77
+
78
+ return (
79
+ <WrapperModal title={`Supply ${poolUnderlyingToken.symbol}`}>
80
+ <FormControl>
81
+ <FormLabel css={styles.subTitle}>Select Tranche Type</FormLabel>
82
+ <RadioGroup
83
+ aria-labelledby='buttons-group-label'
84
+ name='radio-buttons-group'
85
+ css={styles.radioGroup}
86
+ onChange={(e) => changeTranche(e.target.value as TrancheType)}
87
+ >
88
+ {items.map((item) => (
89
+ <FormControlLabel
90
+ key={item.label}
91
+ value={item.value}
92
+ control={
93
+ <Radio
94
+ sx={{
95
+ '& .MuiSvgIcon-root': {
96
+ fontSize: 24,
97
+ },
98
+ }}
99
+ />
100
+ }
101
+ label={item.label}
102
+ css={styles.formControl}
103
+ />
104
+ ))}
105
+ </RadioGroup>
106
+ </FormControl>
107
+ <BottomButton
108
+ variant='contained'
109
+ disabled={!selectedTranche}
110
+ onClick={handleNext}
111
+ >
112
+ NEXT
113
+ </BottomButton>
114
+ </WrapperModal>
115
+ )
116
+ }
@@ -0,0 +1,130 @@
1
+ import {
2
+ formatNumber,
3
+ SolanaPoolInfo,
4
+ SolanaTokenUtils,
5
+ TrancheType,
6
+ } from '@huma-finance/shared'
7
+ import { SolanaPoolState } from '@huma-finance/web-shared'
8
+ import React, { useMemo, useState } from 'react'
9
+ import { BN } from '@coral-xyz/anchor'
10
+ import { Account } from '@solana/spl-token'
11
+ import { useAppDispatch } from '../../../hooks/useRedux'
12
+ import { setStep, setSupplyAmount } from '../../../store/widgets.reducers'
13
+ import { WIDGET_STEP } from '../../../store/widgets.store'
14
+ import { InputAmountModal } from '../../InputAmountModal'
15
+
16
+ type Props = {
17
+ poolInfo: SolanaPoolInfo
18
+ poolState: SolanaPoolState
19
+ tokenAccount: Account
20
+ selectedTranche: TrancheType | undefined
21
+ isUniTranche: boolean
22
+ }
23
+
24
+ export function ChooseAmount({
25
+ poolInfo,
26
+ poolState,
27
+ tokenAccount,
28
+ selectedTranche,
29
+ isUniTranche,
30
+ }: Props): React.ReactElement | null {
31
+ const dispatch = useAppDispatch()
32
+ const { symbol, decimals } = poolInfo.underlyingMint
33
+ const [currentAmount, setCurrentAmount] = useState<number | string>(0)
34
+ const balance = useMemo(
35
+ () => new BN(tokenAccount.amount),
36
+ [tokenAccount.amount],
37
+ )
38
+
39
+ const { juniorAvailableCapBN, seniorAvailableCapBN } = useMemo(() => {
40
+ const {
41
+ maxSeniorJuniorRatio,
42
+ liquidityCap,
43
+ seniorTrancheAssets,
44
+ juniorTrancheAssets,
45
+ } = poolState
46
+ const juniorTrancheAssetsBN = new BN(juniorTrancheAssets)
47
+ const seniorTrancheAssetsBN = new BN(seniorTrancheAssets)
48
+ const totalDeployedBN = seniorTrancheAssetsBN.add(juniorTrancheAssetsBN)
49
+ const totalAvailableCapBN = new BN(liquidityCap).sub(totalDeployedBN)
50
+ const maxSeniorAssetsBN = juniorTrancheAssetsBN.muln(maxSeniorJuniorRatio)
51
+ let seniorAvailableCapBN = maxSeniorAssetsBN.sub(seniorTrancheAssetsBN)
52
+ seniorAvailableCapBN = seniorAvailableCapBN.gt(totalAvailableCapBN)
53
+ ? totalAvailableCapBN
54
+ : seniorAvailableCapBN
55
+
56
+ return {
57
+ juniorAvailableCapBN: totalAvailableCapBN.sub(seniorAvailableCapBN),
58
+ seniorAvailableCapBN,
59
+ }
60
+ }, [poolState])
61
+
62
+ const handleChangeAmount = (newAmount: number) => {
63
+ setCurrentAmount(newAmount)
64
+ dispatch(setSupplyAmount(Number(newAmount)))
65
+ }
66
+
67
+ const handleAction = () => {
68
+ dispatch(setStep(WIDGET_STEP.Transfer))
69
+ }
70
+
71
+ const getTrancheCap = () => {
72
+ if (selectedTranche === 'junior') {
73
+ return juniorAvailableCapBN
74
+ }
75
+ return seniorAvailableCapBN
76
+ }
77
+
78
+ const getMinAmount = (): number => {
79
+ const minAmount = poolState.minDepositAmount
80
+ ? SolanaTokenUtils.formatUnits(
81
+ new BN(poolState.minDepositAmount),
82
+ decimals,
83
+ )
84
+ : 0
85
+ return Math.max(0, Math.ceil(Number(minAmount)))
86
+ }
87
+
88
+ const getMaxAmount = (): BN => {
89
+ const trancheCap = getTrancheCap()
90
+ return balance.gt(trancheCap) ? trancheCap : balance
91
+ }
92
+
93
+ const getInfos = () => {
94
+ const trancheCap = getTrancheCap()
95
+ const currentAmountBN = SolanaTokenUtils.parseUnits(
96
+ String(currentAmount),
97
+ decimals,
98
+ )
99
+ const remainingCap = trancheCap.sub(currentAmountBN)
100
+
101
+ if (remainingCap.ltn(0)) {
102
+ return ['0 remaining capacity']
103
+ }
104
+ return [
105
+ `${formatNumber(
106
+ SolanaTokenUtils.formatUnits(remainingCap, decimals),
107
+ )} remaining capacity`,
108
+ ]
109
+ }
110
+
111
+ return (
112
+ <InputAmountModal
113
+ title='Enter Amount'
114
+ subTitle={`Depositing to ${
115
+ isUniTranche ? 'uni tranche' : selectedTranche
116
+ } tranche`}
117
+ tokenSymbol={symbol}
118
+ currentAmount={currentAmount}
119
+ handleChangeAmount={handleChangeAmount}
120
+ minimumAmount={getMinAmount()}
121
+ maxAmount={Number(SolanaTokenUtils.formatUnits(getMaxAmount(), decimals))}
122
+ maxAmountTitle={`${formatNumber(
123
+ SolanaTokenUtils.formatUnits(balance, decimals),
124
+ )} balance`}
125
+ infos={getInfos()}
126
+ handleAction={handleAction}
127
+ actionText='SUPPLY'
128
+ />
129
+ )
130
+ }
@@ -0,0 +1,89 @@
1
+ import {
2
+ getTokenAccounts,
3
+ SolanaPoolInfo,
4
+ SolanaTokenUtils,
5
+ TrancheType,
6
+ } from '@huma-finance/shared'
7
+ import React, { useCallback, useEffect, useState } from 'react'
8
+
9
+ import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'
10
+ import { useWallet } from '@solana/wallet-adapter-react'
11
+ import { useHumaProgram } from '@huma-finance/web-shared'
12
+ import { Transaction } from '@solana/web3.js'
13
+ import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux'
14
+ import { setStep } from '../../../store/widgets.reducers'
15
+ import { WIDGET_STEP } from '../../../store/widgets.store'
16
+ import { selectWidgetState } from '../../../store/widgets.selectors'
17
+ import { SolanaTxSendModal } from '../../SolanaTxSendModal'
18
+
19
+ type Props = {
20
+ poolInfo: SolanaPoolInfo
21
+ selectedTranche: TrancheType
22
+ }
23
+
24
+ export function Transfer({
25
+ poolInfo,
26
+ selectedTranche,
27
+ }: Props): React.ReactElement | null {
28
+ const dispatch = useAppDispatch()
29
+ const { publicKey } = useWallet()
30
+ const { supplyAmount } = useAppSelector(selectWidgetState)
31
+ const { decimals } = poolInfo.underlyingMint
32
+ const supplyBigNumber = SolanaTokenUtils.parseUnits(
33
+ String(supplyAmount),
34
+ decimals,
35
+ )
36
+ const [transaction, setTransaction] = useState<Transaction>()
37
+ const program = useHumaProgram(poolInfo.chainId)
38
+
39
+ const handleSuccess = useCallback(() => {
40
+ dispatch(setStep(WIDGET_STEP.Done))
41
+ }, [dispatch])
42
+
43
+ useEffect(() => {
44
+ async function getTx() {
45
+ if (!publicKey || transaction) {
46
+ return
47
+ }
48
+
49
+ const { underlyingTokenATA, seniorTrancheATA, juniorTrancheATA } =
50
+ getTokenAccounts(poolInfo, publicKey)
51
+ const tx = await program.methods
52
+ .deposit(supplyBigNumber)
53
+ .accountsPartial({
54
+ depositor: publicKey,
55
+ poolConfig: poolInfo.poolConfig,
56
+ underlyingMint: poolInfo.underlyingMint.address,
57
+ trancheMint:
58
+ selectedTranche === 'senior'
59
+ ? poolInfo.seniorTrancheMint
60
+ : poolInfo.juniorTrancheMint,
61
+ poolUnderlyingToken: poolInfo.poolUnderlyingTokenAccount,
62
+ depositorUnderlyingToken: underlyingTokenATA,
63
+ depositorTrancheToken:
64
+ selectedTranche === 'senior' ? seniorTrancheATA : juniorTrancheATA,
65
+ humaConfig: poolInfo.humaConfig,
66
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
67
+ })
68
+ .transaction()
69
+
70
+ setTransaction(tx)
71
+ }
72
+ getTx()
73
+ }, [
74
+ poolInfo,
75
+ program.methods,
76
+ publicKey,
77
+ selectedTranche,
78
+ supplyBigNumber,
79
+ transaction,
80
+ ])
81
+
82
+ return (
83
+ <SolanaTxSendModal
84
+ tx={transaction}
85
+ chainId={poolInfo.chainId}
86
+ handleSuccess={handleSuccess}
87
+ />
88
+ )
89
+ }
@@ -0,0 +1,50 @@
1
+ import { formatMoney, SolanaPoolInfo, timeUtil } from '@huma-finance/shared'
2
+ import { SolanaPoolState } from '@huma-finance/web-shared'
3
+ import moment from 'moment'
4
+ import React from 'react'
5
+
6
+ import { selectWidgetState } from '../../../store/widgets.selectors'
7
+ import { useAppSelector } from '../../../hooks/useRedux'
8
+ import { SolanaTxDoneModal } from '../../SolanaTxDoneModal'
9
+
10
+ type Props = {
11
+ poolInfo: SolanaPoolInfo
12
+ poolState: SolanaPoolState
13
+ handleAction: () => void
14
+ }
15
+
16
+ export function Success({
17
+ poolInfo,
18
+ poolState,
19
+ handleAction,
20
+ }: Props): React.ReactElement {
21
+ const { supplyAmount, solanaSignature } = useAppSelector(selectWidgetState)
22
+ const { symbol } = poolInfo.underlyingMint
23
+
24
+ const content = [
25
+ `You successfully supplied ${formatMoney(supplyAmount)} ${symbol}.`,
26
+ ]
27
+
28
+ const getSubContent = () => {
29
+ const currentTime = moment().add(
30
+ poolState.withdrawalLockupPeriodDays,
31
+ 'days',
32
+ )
33
+ return [
34
+ `First redemption date: ${timeUtil.timestampToLL(
35
+ currentTime.unix(),
36
+ )}. You can redeem end of each month after.`,
37
+ ]
38
+ }
39
+
40
+ return (
41
+ <SolanaTxDoneModal
42
+ handleAction={handleAction}
43
+ content={content}
44
+ subContent={getSubContent()}
45
+ chainId={poolInfo.chainId}
46
+ solanaSignature={solanaSignature}
47
+ buttonText='DONE'
48
+ />
49
+ )
50
+ }
@@ -0,0 +1,143 @@
1
+ import { SolanaPoolInfo, TrancheType } from '@huma-finance/shared'
2
+ import {
3
+ SolanaPoolState,
4
+ useLenderAccounts,
5
+ useTokenAccount,
6
+ } from '@huma-finance/web-shared'
7
+ import React, { useEffect, useState } from 'react'
8
+ import { useDispatch } from 'react-redux'
9
+
10
+ import { useAppSelector } from '../../../hooks/useRedux'
11
+ import { selectWidgetState } from '../../../store/widgets.selectors'
12
+ import { WidgetWrapper } from '../../WidgetWrapper'
13
+ import { WIDGET_STEP } from '../../../store/widgets.store'
14
+ import { ChooseTranche } from './1-ChooseTranche'
15
+ import { ChooseAmount } from './3-ChooseAmount'
16
+ import { setStep } from '../../../store/widgets.reducers'
17
+ import { ErrorModal } from '../../ErrorModal'
18
+ import { Transfer } from './4-Transfer'
19
+ import { Success } from './5-Success'
20
+
21
+ /**
22
+ * Lend pool supply props
23
+ * @typedef {Object} SolanaLendSupplyProps
24
+ * @property {SolanaPoolInfo} poolInfo The metadata of the pool.
25
+ * @property {SolanaPoolState} poolState The current state config of the pool.
26
+ * @property {function():void} handleClose Function to notify to close the widget modal when user clicks the 'x' close button.
27
+ * @property {function():void|undefined} handleSuccess Optional function to notify that the lending pool supply action is successful.
28
+ */
29
+ export interface SolanaLendSupplyProps {
30
+ poolInfo: SolanaPoolInfo
31
+ poolState: SolanaPoolState
32
+ handleClose: () => void
33
+ handleSuccess?: () => void
34
+ }
35
+
36
+ export function SolanaLendSupply({
37
+ poolInfo,
38
+ poolState,
39
+ handleClose,
40
+ handleSuccess,
41
+ }: SolanaLendSupplyProps): React.ReactElement | null {
42
+ const dispatch = useDispatch()
43
+ const {
44
+ seniorLenderApproved,
45
+ juniorLenderApproved,
46
+ loading: isLoadingLenderAccounts,
47
+ } = useLenderAccounts(poolInfo.chainId, poolInfo.poolName)
48
+ const [tokenAccount, isLoadingTokenAccount] = useTokenAccount(poolInfo)
49
+ const { step, errorMessage } = useAppSelector(selectWidgetState)
50
+ const isUniTranche = poolState.maxSeniorJuniorRatio === 0
51
+ const [selectedTranche, setSelectedTranche] = useState<TrancheType>()
52
+
53
+ useEffect(() => {
54
+ if (!step && !isLoadingLenderAccounts && !isLoadingTokenAccount) {
55
+ if (juniorLenderApproved && !seniorLenderApproved) {
56
+ setSelectedTranche('junior')
57
+ dispatch(setStep(WIDGET_STEP.ChooseAmount))
58
+ return
59
+ }
60
+
61
+ if (seniorLenderApproved && !juniorLenderApproved) {
62
+ setSelectedTranche('senior')
63
+ dispatch(setStep(WIDGET_STEP.ChooseAmount))
64
+ return
65
+ }
66
+
67
+ if (juniorLenderApproved && seniorLenderApproved) {
68
+ dispatch(setStep(WIDGET_STEP.ChooseTranche))
69
+ // return
70
+ }
71
+
72
+ // if (poolInfo.supplyLink) {
73
+ // openInNewTab(poolInfo.supplyLink)
74
+ // handleClose()
75
+ // }
76
+ }
77
+ }, [
78
+ dispatch,
79
+ handleClose,
80
+ poolInfo,
81
+ step,
82
+ isLoadingLenderAccounts,
83
+ juniorLenderApproved,
84
+ seniorLenderApproved,
85
+ isLoadingTokenAccount,
86
+ ])
87
+
88
+ if (isLoadingLenderAccounts || isLoadingTokenAccount) {
89
+ return (
90
+ <WidgetWrapper
91
+ isOpen
92
+ isLoading
93
+ loadingTitle='Supply'
94
+ handleClose={handleClose}
95
+ handleSuccess={handleSuccess}
96
+ />
97
+ )
98
+ }
99
+
100
+ return (
101
+ <WidgetWrapper
102
+ isOpen
103
+ loadingTitle={`Supply ${poolInfo.underlyingMint.symbol}`}
104
+ handleClose={handleClose}
105
+ handleSuccess={handleSuccess}
106
+ >
107
+ {step === WIDGET_STEP.ChooseTranche && (
108
+ <ChooseTranche
109
+ poolUnderlyingToken={poolInfo.underlyingMint}
110
+ selectedTranche={selectedTranche}
111
+ changeTranche={setSelectedTranche}
112
+ />
113
+ )}
114
+ {step === WIDGET_STEP.ChooseAmount && (
115
+ <ChooseAmount
116
+ poolInfo={poolInfo}
117
+ poolState={poolState}
118
+ tokenAccount={tokenAccount!}
119
+ selectedTranche={selectedTranche}
120
+ isUniTranche={isUniTranche}
121
+ />
122
+ )}
123
+ {step === WIDGET_STEP.Transfer && selectedTranche && (
124
+ <Transfer poolInfo={poolInfo} selectedTranche={selectedTranche} />
125
+ )}
126
+ {step === WIDGET_STEP.Done && (
127
+ <Success
128
+ poolInfo={poolInfo}
129
+ poolState={poolState}
130
+ handleAction={handleClose}
131
+ />
132
+ )}
133
+ {step === WIDGET_STEP.Error && (
134
+ <ErrorModal
135
+ title='Supply'
136
+ errorReason='Sorry there was an error'
137
+ errorMessage={errorMessage}
138
+ handleOk={handleClose}
139
+ />
140
+ )}
141
+ </WidgetWrapper>
142
+ )
143
+ }
@@ -0,0 +1,115 @@
1
+ import { Box, Button, css, Typography, useTheme } from '@mui/material'
2
+ import React, { useCallback } from 'react'
3
+
4
+ import { SolanaChainEnum } from '@huma-finance/shared'
5
+ import { useAppDispatch } from '../hooks/useRedux'
6
+ import { resetState } from '../store/widgets.reducers'
7
+ import { CheckIcon } from './icons'
8
+ import { SolanaViewOnExplorer } from './SolanaViewOnExplorer'
9
+
10
+ type Props = {
11
+ content: string[]
12
+ subContent?: string[]
13
+ buttonText?: string
14
+ solanaSignature?: string
15
+ chainId?: SolanaChainEnum
16
+ handleAction: () => void
17
+ }
18
+
19
+ export function SolanaTxDoneModal({
20
+ content,
21
+ subContent,
22
+ handleAction,
23
+ solanaSignature,
24
+ chainId,
25
+ buttonText,
26
+ }: Props): React.ReactElement {
27
+ const theme = useTheme()
28
+ const dispatch = useAppDispatch()
29
+
30
+ const styles = {
31
+ wrapper: css`
32
+ height: 518px;
33
+ position: relative;
34
+ `,
35
+ header: css`
36
+ ${theme.cssMixins.rowHCentered};
37
+ margin-top: ${theme.spacing(-0.5)};
38
+ `,
39
+ content: css`
40
+ ${theme.cssMixins.colVCentered};
41
+ font-weight: ${subContent ? 700 : 400};
42
+ font-size: 18px;
43
+ color: ${theme.palette.text.secondary};
44
+ margin-top: ${theme.spacing(8)};
45
+ text-align: center;
46
+ `,
47
+ subContent: css`
48
+ ${theme.cssMixins.colVCentered};
49
+ font-weight: 400;
50
+ font-size: 18px;
51
+ color: ${theme.palette.text.primary};
52
+ margin-top: ${theme.spacing(2)};
53
+ text-align: center;
54
+ `,
55
+ check: css`
56
+ width: 100%;
57
+ ${theme.cssMixins.rowHCentered};
58
+ margin-top: ${theme.spacing(10)};
59
+ `,
60
+ bottomButtonGroup: css`
61
+ width: 100%;
62
+ position: absolute;
63
+ bottom: 0;
64
+ `,
65
+ bottomButton: css`
66
+ margin-top: ${theme.spacing(1)};
67
+ `,
68
+ }
69
+
70
+ const handleCloseModal = useCallback(() => {
71
+ dispatch(resetState())
72
+ handleAction()
73
+ }, [dispatch, handleAction])
74
+
75
+ return (
76
+ <Box css={styles.wrapper}>
77
+ <Typography variant='h6' css={styles.header}>
78
+ Transaction Completed
79
+ </Typography>
80
+ <Box css={styles.check}>
81
+ <CheckIcon />
82
+ </Box>
83
+ <Box css={styles.content}>
84
+ {content.map((item) => (
85
+ <Box sx={{ marginTop: theme.spacing(1) }} key={item}>
86
+ {item}
87
+ </Box>
88
+ ))}
89
+ </Box>
90
+ {subContent && (
91
+ <Box css={styles.subContent}>
92
+ {subContent.map((item) => (
93
+ <Box sx={{ marginTop: theme.spacing(1) }} key={item}>
94
+ {item}
95
+ </Box>
96
+ ))}
97
+ </Box>
98
+ )}
99
+ <Box css={styles.bottomButtonGroup}>
100
+ {solanaSignature && chainId && (
101
+ <SolanaViewOnExplorer signature={solanaSignature} chainId={chainId} />
102
+ )}
103
+ <Button
104
+ className='transaction-done-modal-close-btn'
105
+ variant='contained'
106
+ fullWidth
107
+ css={styles.bottomButton}
108
+ onClick={handleCloseModal}
109
+ >
110
+ {!buttonText ? 'DONE' : buttonText}
111
+ </Button>
112
+ </Box>
113
+ </Box>
114
+ )
115
+ }
@@ -0,0 +1,61 @@
1
+ import { SolanaChainEnum } from '@huma-finance/shared'
2
+ import React, { useEffect, useState } from 'react'
3
+
4
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react'
5
+ import { Transaction } from '@solana/web3.js'
6
+ import { setError, setSolanaSignature } from '../store/widgets.reducers'
7
+ import { LoadingModal } from './LoadingModal'
8
+ import { SolanaViewOnExplorer } from './SolanaViewOnExplorer'
9
+ import { useAppDispatch } from '../hooks/useRedux'
10
+
11
+ type Props = {
12
+ chainId: SolanaChainEnum
13
+ tx?: Transaction
14
+ handleSuccess: () => void
15
+ }
16
+
17
+ export function SolanaTxSendModal({
18
+ chainId,
19
+ tx,
20
+ handleSuccess,
21
+ }: Props): React.ReactElement | null {
22
+ const dispatch = useAppDispatch()
23
+ const { sendTransaction } = useWallet()
24
+ const { connection } = useConnection()
25
+ const [signature, setSignature] = useState<string>('')
26
+
27
+ useEffect(() => {
28
+ async function sendTx() {
29
+ if (!connection || !tx) {
30
+ return
31
+ }
32
+
33
+ try {
34
+ const latestBlockHash = await connection.getLatestBlockhash()
35
+ const signature = await sendTransaction(tx, connection)
36
+ setSignature(signature)
37
+ dispatch(setSolanaSignature(signature))
38
+
39
+ await connection.confirmTransaction({
40
+ blockhash: latestBlockHash.blockhash,
41
+ lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
42
+ signature,
43
+ })
44
+ handleSuccess()
45
+ } catch (error: unknown) {
46
+ const err = error as Error
47
+ dispatch(setError({ errorMessage: err?.message || '' }))
48
+ }
49
+ }
50
+ sendTx()
51
+ }, [connection, dispatch, handleSuccess, sendTransaction, tx])
52
+
53
+ return (
54
+ <LoadingModal
55
+ title='Transaction Pending'
56
+ description='Waiting for confirmation...'
57
+ >
58
+ <SolanaViewOnExplorer chainId={chainId} signature={signature} />
59
+ </LoadingModal>
60
+ )
61
+ }