@huma-finance/widgets 0.0.78-beta.1034 → 0.0.78-beta.1044

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 (42) hide show
  1. package/dist/index.js +12133 -10900
  2. package/dist/src/components/Lend/solanaWithdraw/1-Option.d.ts +12 -0
  3. package/dist/src/components/Lend/solanaWithdraw/2-WithdrawAndDepositConfirm.d.ts +15 -0
  4. package/dist/src/components/Lend/solanaWithdraw/{1-ConfirmTransfer.d.ts → 3-WithdrawOnlyConfirm.d.ts} +1 -1
  5. package/dist/src/components/Lend/solanaWithdraw/4-TransferAndDeposit.d.ts +13 -0
  6. package/dist/src/components/Lend/solanaWithdraw/{3-Done.d.ts → 6-Done.d.ts} +3 -1
  7. package/dist/src/components/Lend/solanaWithdraw/components/Apy.d.ts +7 -0
  8. package/dist/src/components/Lend/solanaWithdraw/components/Benefit.d.ts +2 -0
  9. package/dist/src/components/Lend/solanaWithdraw/components/CommitSwitcher.d.ts +12 -0
  10. package/dist/src/components/Lend/solanaWithdraw/components/ModeSelector.d.ts +12 -0
  11. package/dist/src/components/Lend/solanaWithdraw/components/SelectModeTitle.d.ts +2 -0
  12. package/dist/src/components/Lend/solanaWithdraw/index.d.ts +10 -0
  13. package/dist/src/components/TxDoneModal.d.ts +1 -1
  14. package/dist/src/components/WidgetWrapper.d.ts +2 -1
  15. package/dist/src/components/icons/index.d.ts +7 -0
  16. package/dist/src/store/widgets.store.d.ts +3 -0
  17. package/package.json +5 -5
  18. package/src/components/Lend/solanaWithdraw/1-Option.tsx +180 -0
  19. package/src/components/Lend/solanaWithdraw/2-WithdrawAndDepositConfirm.tsx +208 -0
  20. package/src/components/Lend/solanaWithdraw/{1-ConfirmTransfer.tsx → 3-WithdrawOnlyConfirm.tsx} +2 -2
  21. package/src/components/Lend/solanaWithdraw/4-TransferAndDeposit.tsx +274 -0
  22. package/src/components/Lend/solanaWithdraw/{3-Done.tsx → 6-Done.tsx} +27 -3
  23. package/src/components/Lend/solanaWithdraw/components/Apy.tsx +126 -0
  24. package/src/components/Lend/solanaWithdraw/components/Benefit.tsx +63 -0
  25. package/src/components/Lend/solanaWithdraw/components/CommitSwitcher.tsx +198 -0
  26. package/src/components/Lend/solanaWithdraw/components/ModeSelector.tsx +148 -0
  27. package/src/components/Lend/solanaWithdraw/components/SelectModeTitle.tsx +61 -0
  28. package/src/components/Lend/solanaWithdraw/index.tsx +102 -12
  29. package/src/components/TxDoneModal.tsx +3 -3
  30. package/src/components/WidgetWrapper.tsx +3 -1
  31. package/src/components/icons/huma-no-empty-space.svg +15 -0
  32. package/src/components/icons/index.tsx +49 -14
  33. package/src/components/icons/minus-sign-inactive.svg +4 -0
  34. package/src/components/icons/minus-sign.svg +14 -0
  35. package/src/components/icons/mode-classic.svg +3 -0
  36. package/src/components/icons/mode-maxi.svg +3 -0
  37. package/src/components/icons/plus-sign-inactive.svg +4 -0
  38. package/src/components/icons/plus-sign.svg +14 -0
  39. package/src/hooks/useLogOnFirstMount.ts +1 -1
  40. package/src/store/widgets.store.ts +3 -0
  41. /package/dist/src/components/Lend/solanaWithdraw/{2-Transfer.d.ts → 5-Transfer.d.ts} +0 -0
  42. /package/src/components/Lend/solanaWithdraw/{2-Transfer.tsx → 5-Transfer.tsx} +0 -0
@@ -0,0 +1,274 @@
1
+ import { BN } from '@coral-xyz/anchor'
2
+ import {
3
+ getSolanaNetworkType,
4
+ getTokenAccounts,
5
+ PermissionlessDepositCommitment,
6
+ PermissionlessDepositMode,
7
+ PermissionlessService,
8
+ SOLANA_CHAIN_INFO_PERMISSIONLESS,
9
+ SolanaPoolInfo,
10
+ TrancheType,
11
+ } from '@huma-finance/shared'
12
+ import {
13
+ checkIsDev,
14
+ logErrorRaw,
15
+ useHumaProgram,
16
+ usePermissionlessLenderModeATA,
17
+ usePermissionlessLenderStateAccount,
18
+ usePermissionlessProgram,
19
+ } from '@huma-finance/web-shared'
20
+ import {
21
+ ASSOCIATED_TOKEN_PROGRAM_ID,
22
+ createAssociatedTokenAccountIdempotentInstruction,
23
+ createAssociatedTokenAccountInstruction,
24
+ getAccount,
25
+ TOKEN_2022_PROGRAM_ID,
26
+ TOKEN_PROGRAM_ID,
27
+ TokenAccountNotFoundError,
28
+ TokenInvalidAccountOwnerError,
29
+ } from '@solana/spl-token'
30
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react'
31
+ import { ComputeBudgetProgram, PublicKey, Transaction } from '@solana/web3.js'
32
+ import React, { useCallback, useEffect, useState } from 'react'
33
+ import useLogOnFirstMount from '../../../hooks/useLogOnFirstMount'
34
+ import { useAppDispatch } from '../../../hooks/useRedux'
35
+ import { setStep } from '../../../store/widgets.reducers'
36
+ import { WIDGET_STEP } from '../../../store/widgets.store'
37
+ import { SolanaTxSendModal } from '../../SolanaTxSendModal'
38
+
39
+ type Props = {
40
+ poolInfo: SolanaPoolInfo
41
+ selectedTranche: TrancheType
42
+ poolIsClosed: boolean
43
+ permissionlessMode: PermissionlessDepositMode
44
+ withdrawableAmount: BN
45
+ depositCommitment: PermissionlessDepositCommitment
46
+ }
47
+
48
+ export function TransferAndDeposit({
49
+ poolInfo,
50
+ selectedTranche,
51
+ poolIsClosed,
52
+ permissionlessMode,
53
+ withdrawableAmount,
54
+ depositCommitment,
55
+ }: Props): React.ReactElement | null {
56
+ useLogOnFirstMount('RedepositTransaction')
57
+ const { publicKey } = useWallet()
58
+ const dispatch = useAppDispatch()
59
+ const { connection } = useConnection()
60
+ const program = useHumaProgram(poolInfo.chainId)
61
+ const programPermissionless = usePermissionlessProgram()
62
+ const [transaction, setTransaction] = useState<Transaction>()
63
+ const { lenderStateAccount, isLoaded: isLoadedLenderStateAccount } =
64
+ usePermissionlessLenderStateAccount(poolInfo.chainId, permissionlessMode)
65
+ const solanaChainInfoPermissionless =
66
+ SOLANA_CHAIN_INFO_PERMISSIONLESS[poolInfo.chainId]
67
+ const { lenderClassicModeTokenATA, lenderMaxiModeTokenATA } =
68
+ usePermissionlessLenderModeATA(poolInfo.chainId)
69
+
70
+ useEffect(() => {
71
+ async function getTx() {
72
+ if (
73
+ !publicKey ||
74
+ transaction ||
75
+ !connection ||
76
+ !isLoadedLenderStateAccount ||
77
+ !lenderClassicModeTokenATA ||
78
+ !lenderMaxiModeTokenATA
79
+ ) {
80
+ return
81
+ }
82
+
83
+ const tx = new Transaction()
84
+ const { underlyingTokenATA, seniorTrancheATA, juniorTrancheATA } =
85
+ getTokenAccounts(poolInfo, publicKey)
86
+ const trancheMint =
87
+ selectedTranche === 'senior'
88
+ ? poolInfo.seniorTrancheMint
89
+ : poolInfo.juniorTrancheMint
90
+ const lenderTrancheToken =
91
+ selectedTranche === 'senior' ? seniorTrancheATA : juniorTrancheATA
92
+
93
+ // Create user token account if it doesn't exist
94
+ let createdAccounts = false
95
+ try {
96
+ await getAccount(
97
+ connection,
98
+ underlyingTokenATA,
99
+ undefined,
100
+ TOKEN_PROGRAM_ID,
101
+ )
102
+ } catch (error: unknown) {
103
+ // TokenAccountNotFoundError can be possible if the associated address has already received some lamports,
104
+ // becoming a system account. Assuming program derived addressing is safe, this is the only case for the
105
+ // TokenInvalidAccountOwnerError in this code path.
106
+ // Source: https://solana.stackexchange.com/questions/802/checking-to-see-if-a-token-account-exists-using-anchor-ts
107
+ if (
108
+ error instanceof TokenAccountNotFoundError ||
109
+ error instanceof TokenInvalidAccountOwnerError
110
+ ) {
111
+ // As this isn't atomic, it's possible others can create associated accounts meanwhile.
112
+ createdAccounts = true
113
+ tx.add(
114
+ createAssociatedTokenAccountInstruction(
115
+ publicKey,
116
+ underlyingTokenATA,
117
+ publicKey,
118
+ new PublicKey(poolInfo.underlyingMint.address),
119
+ TOKEN_PROGRAM_ID,
120
+ ASSOCIATED_TOKEN_PROGRAM_ID,
121
+ ),
122
+ )
123
+ }
124
+ }
125
+
126
+ if (!poolIsClosed) {
127
+ const disburseTx = await program.methods
128
+ .disburse()
129
+ .accountsPartial({
130
+ lender: publicKey,
131
+ humaConfig: poolInfo.humaConfig,
132
+ poolConfig: poolInfo.poolConfig,
133
+ underlyingMint: poolInfo.underlyingMint.address,
134
+ trancheMint,
135
+ poolUnderlyingToken: poolInfo.poolUnderlyingTokenAccount,
136
+ lenderUnderlyingToken: underlyingTokenATA,
137
+ tokenProgram: TOKEN_PROGRAM_ID,
138
+ })
139
+ .transaction()
140
+ tx.add(disburseTx)
141
+ } else {
142
+ const withdrawAfterPoolClosureTx = await program.methods
143
+ .withdrawAfterPoolClosure()
144
+ .accountsPartial({
145
+ lender: publicKey,
146
+ humaConfig: poolInfo.humaConfig,
147
+ poolConfig: poolInfo.poolConfig,
148
+ underlyingMint: poolInfo.underlyingMint.address,
149
+ trancheMint,
150
+ poolUnderlyingToken: poolInfo.poolUnderlyingTokenAccount,
151
+ lenderUnderlyingToken: underlyingTokenATA,
152
+ lenderTrancheToken,
153
+ underlyingTokenProgram: TOKEN_PROGRAM_ID,
154
+ trancheTokenProgram: TOKEN_2022_PROGRAM_ID,
155
+ })
156
+ .transaction()
157
+ tx.add(withdrawAfterPoolClosureTx)
158
+ }
159
+
160
+ const permissionlessModeConfig =
161
+ permissionlessMode === PermissionlessDepositMode.CLASSIC
162
+ ? solanaChainInfoPermissionless.classicModeConfig
163
+ : solanaChainInfoPermissionless.maxiModeConfig
164
+ const permissonlessLenderModeTokenATA =
165
+ permissionlessMode === PermissionlessDepositMode.CLASSIC
166
+ ? lenderClassicModeTokenATA
167
+ : lenderMaxiModeTokenATA
168
+
169
+ if (!lenderStateAccount) {
170
+ const createLenderAccountInstruction =
171
+ await programPermissionless.methods
172
+ .createLenderAccounts()
173
+ .accountsPartial({
174
+ lender: publicKey,
175
+ humaConfig: solanaChainInfoPermissionless.humaConfig,
176
+ poolConfig: solanaChainInfoPermissionless.poolConfig,
177
+ modeConfig: permissionlessModeConfig,
178
+ lenderModeToken: permissonlessLenderModeTokenATA,
179
+ tokenProgram: TOKEN_PROGRAM_ID,
180
+ })
181
+ .transaction()
182
+ tx.add(createLenderAccountInstruction)
183
+ }
184
+
185
+ tx.add(
186
+ createAssociatedTokenAccountIdempotentInstruction(
187
+ publicKey,
188
+ permissonlessLenderModeTokenATA,
189
+ publicKey,
190
+ new PublicKey(
191
+ permissionlessMode === PermissionlessDepositMode.CLASSIC
192
+ ? solanaChainInfoPermissionless.classicModeMint
193
+ : solanaChainInfoPermissionless.maxiModeMint,
194
+ ),
195
+ TOKEN_PROGRAM_ID,
196
+ ),
197
+ )
198
+
199
+ const depositTx = await programPermissionless.methods
200
+ .deposit(withdrawableAmount, depositCommitment, false)
201
+ .accountsPartial({
202
+ depositor: publicKey,
203
+ humaConfig: solanaChainInfoPermissionless.humaConfig,
204
+ poolConfig: solanaChainInfoPermissionless.poolConfig,
205
+ modeConfig: permissionlessModeConfig,
206
+ underlyingMint: solanaChainInfoPermissionless.underlyingMint,
207
+ underlyingTokenProgram: TOKEN_PROGRAM_ID,
208
+ modeTokenProgram: TOKEN_PROGRAM_ID,
209
+ })
210
+ .transaction()
211
+ tx.add(depositTx)
212
+
213
+ if (createdAccounts) {
214
+ tx.instructions.unshift(
215
+ ComputeBudgetProgram.setComputeUnitLimit({
216
+ units: poolIsClosed ? 145_000 : 105_000,
217
+ }),
218
+ )
219
+ }
220
+
221
+ setTransaction(tx)
222
+ }
223
+ getTx()
224
+ }, [
225
+ connection,
226
+ depositCommitment,
227
+ isLoadedLenderStateAccount,
228
+ lenderClassicModeTokenATA,
229
+ lenderMaxiModeTokenATA,
230
+ lenderStateAccount,
231
+ permissionlessMode,
232
+ poolInfo,
233
+ poolIsClosed,
234
+ program.methods,
235
+ programPermissionless.methods,
236
+ publicKey,
237
+ selectedTranche,
238
+ solanaChainInfoPermissionless,
239
+ transaction,
240
+ withdrawableAmount,
241
+ ])
242
+
243
+ const handleSuccess = useCallback(
244
+ async (options?: { signature: string }) => {
245
+ const isDev = checkIsDev()
246
+ if (publicKey && options?.signature) {
247
+ try {
248
+ await PermissionlessService.updatePermissionlessDeposit(
249
+ getSolanaNetworkType(poolInfo.chainId),
250
+ isDev,
251
+ publicKey.toString(),
252
+ poolInfo.chainId,
253
+ options?.signature,
254
+ )
255
+ } catch (error) {
256
+ logErrorRaw(error, {
257
+ message: 'Failed to update permissionless deposit',
258
+ })
259
+ console.error('Failed to update permissionless deposit', error)
260
+ }
261
+ }
262
+ dispatch(setStep(WIDGET_STEP.Done))
263
+ },
264
+ [dispatch, poolInfo.chainId, publicKey],
265
+ )
266
+
267
+ return (
268
+ <SolanaTxSendModal
269
+ tx={transaction}
270
+ chainId={poolInfo.chainId}
271
+ handleSuccess={handleSuccess}
272
+ />
273
+ )
274
+ }
@@ -1,25 +1,34 @@
1
1
  import { BN } from '@coral-xyz/anchor'
2
2
  import {
3
+ configUtil,
3
4
  formatMoneyFixed,
4
5
  SolanaTokenUtils,
5
6
  UnderlyingTokenInfo,
6
7
  } from '@huma-finance/shared'
8
+ import { Link } from '@mui/material'
7
9
  import React from 'react'
8
- import { TxDoneModal } from '../../TxDoneModal'
10
+ import { ClaimAndStakeOption, WithdrawOption } from '.'
9
11
  import useLogOnFirstMount from '../../../hooks/useLogOnFirstMount'
12
+ import { TxDoneModal } from '../../TxDoneModal'
10
13
 
11
14
  type Props = {
12
15
  poolUnderlyingToken: UnderlyingTokenInfo
13
16
  withdrawAmount: BN
17
+ option: ClaimAndStakeOption
14
18
  handleAction: () => void
15
19
  }
16
20
 
17
21
  export function Done({
18
22
  poolUnderlyingToken,
19
23
  withdrawAmount,
24
+ option,
20
25
  handleAction,
21
26
  }: Props): React.ReactElement {
22
- useLogOnFirstMount('Success')
27
+ useLogOnFirstMount(
28
+ option.id === WithdrawOption.WITHDRAW_AND_REDEPOSIT
29
+ ? 'RedepositSuccess'
30
+ : 'WithdrawSuccess',
31
+ )
23
32
  const { symbol } = poolUnderlyingToken
24
33
  const withdrawAmountFormatted = formatMoneyFixed(
25
34
  SolanaTokenUtils.formatUnits(withdrawAmount, poolUnderlyingToken.decimals),
@@ -27,7 +36,22 @@ export function Done({
27
36
  )
28
37
 
29
38
  const content = [
30
- `You successfully withdrew ${withdrawAmountFormatted} ${symbol}.`,
39
+ option.id === WithdrawOption.WITHDRAW_AND_REDEPOSIT ? (
40
+ <>
41
+ You have withdrawn {withdrawAmountFormatted} {symbol} and redeposited to
42
+ Huma Permissionless.{' '}
43
+ <Link
44
+ href={configUtil.dappLink}
45
+ target='_blank'
46
+ rel='noopener noreferrer'
47
+ >
48
+ Go to Huma Permissionless
49
+ </Link>{' '}
50
+ to view your deposit.
51
+ </>
52
+ ) : (
53
+ `You successfully withdrew ${withdrawAmountFormatted} ${symbol}.`
54
+ ),
31
55
  ]
32
56
 
33
57
  return <TxDoneModal handleAction={handleAction} content={content} />
@@ -0,0 +1,126 @@
1
+ import { toPercentage } from '@huma-finance/shared'
2
+ import InfoOutlineIcon from '@mui/icons-material/InfoOutlined'
3
+ import { Box, css, Tooltip, useTheme } from '@mui/material'
4
+ import React from 'react'
5
+ import { HumaNoEmptySpaceIcon, UsdcIcon } from '../../../icons'
6
+
7
+ type Props = {
8
+ usdcApy: number | undefined
9
+ humaRewardsApy: number | undefined
10
+ }
11
+
12
+ export function Apy({ usdcApy, humaRewardsApy }: Props): React.ReactElement {
13
+ const theme = useTheme()
14
+
15
+ const styles = {
16
+ container: css`
17
+ display: flex;
18
+ flex-direction: column;
19
+ margin-top: ${theme.spacing(2)};
20
+ `,
21
+ usdcApy: css`
22
+ display: flex;
23
+ justify-content: space-between;
24
+ align-items: center;
25
+ gap: ${theme.spacing(0.5)};
26
+ `,
27
+ usdcApyTitle: css`
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ gap: ${theme.spacing(0.5)};
32
+ color: #b8b8b8;
33
+ font-size: 16px;
34
+ font-weight: 500;
35
+ line-height: 150%;
36
+ letter-spacing: 0.15px;
37
+ `,
38
+ usdcApyValue: css`
39
+ display: flex;
40
+ align-items: center;
41
+ gap: ${theme.spacing(0.5)};
42
+ color: #b8b8b8;
43
+ font-size: 16px;
44
+ font-weight: 500;
45
+ line-height: 150%;
46
+ letter-spacing: 0.15px;
47
+
48
+ & > svg {
49
+ width: 16px;
50
+ height: 16px;
51
+ }
52
+ `,
53
+ humaRewardsApy: css`
54
+ display: flex;
55
+ justify-content: space-between;
56
+ align-items: center;
57
+ gap: ${theme.spacing(0.5)};
58
+ `,
59
+ humaRewardsApyValue: css`
60
+ display: flex;
61
+ align-items: center;
62
+ gap: ${theme.spacing(0.5)};
63
+ color: #b8b8b8;
64
+ font-size: 16px;
65
+ font-weight: 500;
66
+ line-height: 150%;
67
+ letter-spacing: 0.15px;
68
+
69
+ & > svg {
70
+ width: 16px;
71
+ height: 16px;
72
+ }
73
+ `,
74
+ infoIcon: css`
75
+ width: 16px;
76
+ height: 16px;
77
+ color: #686868;
78
+ `,
79
+ humaRewardsApyTitle: css`
80
+ display: flex;
81
+ justify-content: space-between;
82
+ align-items: center;
83
+ gap: ${theme.spacing(0.5)};
84
+ color: #b8b8b8;
85
+ font-size: 16px;
86
+ font-weight: 500;
87
+ line-height: 150%;
88
+ letter-spacing: 0.15px;
89
+ `,
90
+ }
91
+
92
+ return (
93
+ <Box css={styles.container}>
94
+ <Box css={styles.usdcApy}>
95
+ <Box css={styles.usdcApyTitle}>
96
+ <Box>USDC yield</Box>
97
+ <Tooltip
98
+ title='Current monthly USDC APY for Classic mode.'
99
+ placement='top'
100
+ >
101
+ <InfoOutlineIcon css={styles.infoIcon} />
102
+ </Tooltip>
103
+ </Box>
104
+ <Box css={styles.usdcApyValue}>
105
+ <Box>{toPercentage(usdcApy, 1)}</Box>
106
+ <UsdcIcon />
107
+ </Box>
108
+ </Box>
109
+ <Box css={styles.humaRewardsApy}>
110
+ <Box css={styles.humaRewardsApyTitle}>
111
+ <Box>Huma rewards</Box>
112
+ <Tooltip
113
+ title='Returns are estimated based on the 7-day average price of $HUMA.'
114
+ placement='top'
115
+ >
116
+ <InfoOutlineIcon css={styles.infoIcon} />
117
+ </Tooltip>
118
+ </Box>
119
+ <Box css={styles.humaRewardsApyValue}>
120
+ <Box>{toPercentage(humaRewardsApy, 1)}</Box>
121
+ <HumaNoEmptySpaceIcon />
122
+ </Box>
123
+ </Box>
124
+ </Box>
125
+ )
126
+ }
@@ -0,0 +1,63 @@
1
+ import { Box, css, Link, useTheme } from '@mui/material'
2
+ import React from 'react'
3
+
4
+ export function Benefit(): React.ReactElement {
5
+ const theme = useTheme()
6
+
7
+ const styles = {
8
+ container: css`
9
+ display: flex;
10
+ flex-direction: column;
11
+ margin-top: ${theme.spacing(2)};
12
+ `,
13
+ title: css`
14
+ color: #b8b8b8;
15
+ font-size: 16px;
16
+ font-weight: 700;
17
+ line-height: 175%;
18
+ letter-spacing: 0.15px;
19
+ `,
20
+ list: css`
21
+ margin-top: ${theme.spacing(0)};
22
+ padding-left: ${theme.spacing(2)};
23
+ `,
24
+ listItem: css`
25
+ color: #686868;
26
+ font-size: 14px;
27
+ font-weight: 500;
28
+ line-height: 143%;
29
+ letter-spacing: 0.17px;
30
+ `,
31
+ link: css`
32
+ color: #686868;
33
+ font-size: 14px;
34
+ font-weight: 500;
35
+ line-height: 143%;
36
+ letter-spacing: 0.17px;
37
+ `,
38
+ }
39
+
40
+ return (
41
+ <Box css={styles.container}>
42
+ <Box css={styles.title}>Permissionless benefits</Box>
43
+ <ul css={styles.list}>
44
+ <li css={styles.listItem}>
45
+ Keep your OG status and unlock boosted yield forever
46
+ </li>
47
+ <li css={styles.listItem}>Choose your own investment lockup periods</li>
48
+ <li css={styles.listItem}>Earn incentives for longer commitments</li>
49
+ <li css={styles.listItem}>
50
+ <Link
51
+ href='https://app.huma.finance/faqs'
52
+ target='_blank'
53
+ rel='noopener noreferrer'
54
+ css={styles.link}
55
+ >
56
+ Read more
57
+ </Link>{' '}
58
+ about the Permissionless benefits
59
+ </li>
60
+ </ul>
61
+ </Box>
62
+ )
63
+ }