@unisat/wallet-state 1.0.5 → 1.1.0

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 (99) hide show
  1. package/LICENSE +0 -5
  2. package/lib/index.d.mts +2012 -200
  3. package/lib/index.d.ts +2012 -200
  4. package/lib/index.js +6951 -570
  5. package/lib/index.js.map +1 -1
  6. package/lib/index.mjs +6754 -517
  7. package/lib/index.mjs.map +1 -1
  8. package/lib/types/index.d.mts +1 -1
  9. package/lib/types/index.d.ts +1 -1
  10. package/package.json +19 -15
  11. package/src/.DS_Store +0 -0
  12. package/src/context/ApprovalContext.tsx +27 -0
  13. package/src/context/DeviceContext.tsx +36 -0
  14. package/src/context/I18nContext.tsx +14 -172
  15. package/src/context/NavigationContext.tsx +305 -0
  16. package/src/context/PriceContext.tsx +2 -2
  17. package/src/context/StorageContext.tsx +393 -0
  18. package/src/context/ToolsContext.tsx +50 -0
  19. package/src/context/WalletContext.tsx +119 -108
  20. package/src/context/index.ts +17 -2
  21. package/src/hooks/accounts.ts +11 -5
  22. package/src/hooks/browser.ts +11 -0
  23. package/src/hooks/global.ts +170 -7
  24. package/src/hooks/index.ts +1 -2
  25. package/src/hooks/settings.ts +28 -37
  26. package/src/hooks/transactions.ts +28 -155
  27. package/src/hooks/ui.ts +232 -36
  28. package/src/index.ts +33 -24
  29. package/src/reducers/accounts.ts +19 -2
  30. package/src/reducers/browser.ts +223 -0
  31. package/src/reducers/global.ts +67 -1
  32. package/src/reducers/index.ts +1 -0
  33. package/src/reducers/transactions.ts +0 -9
  34. package/src/reducers/ui.ts +127 -8
  35. package/src/types/index.ts +1 -1
  36. package/src/ui-hooks/index.ts +107 -0
  37. package/src/ui-hooks/useActionOverviewSectionLogic.ts +150 -0
  38. package/src/ui-hooks/useAddressTypeScreenLogic.ts +160 -0
  39. package/src/ui-hooks/useAlkanesBalanceCardLogic.ts +41 -0
  40. package/src/ui-hooks/useAlkanesCollectionListLogic.ts +68 -0
  41. package/src/ui-hooks/useAlkanesListLogic.ts +69 -0
  42. package/src/ui-hooks/useAlkanesNFTListLogic.ts +42 -0
  43. package/src/ui-hooks/useAlkanesNFTScreenLogic.ts +45 -0
  44. package/src/ui-hooks/useAlkanesTokenScreenLogic.ts +130 -0
  45. package/src/ui-hooks/useAmountInputLogic.ts +80 -0
  46. package/src/ui-hooks/useAnnouncementCardLogic.ts +91 -0
  47. package/src/ui-hooks/useBRC20BalanceCardLogic.ts +115 -0
  48. package/src/ui-hooks/useBRC20InscribeTransferLogic.ts +398 -0
  49. package/src/ui-hooks/useBRC20ListLogic.ts +75 -0
  50. package/src/ui-hooks/useBRC20ProgListLogic.ts +77 -0
  51. package/src/ui-hooks/useBRC20SendScreenLogic.ts +411 -0
  52. package/src/ui-hooks/useBRC20SingleStepScreenLogic.ts +208 -0
  53. package/src/ui-hooks/useBRC20TokenScreenLogic.ts +469 -0
  54. package/src/ui-hooks/useBalanceCardLogic.ts +164 -0
  55. package/src/ui-hooks/useBtcDisplayLogic.ts +16 -0
  56. package/src/ui-hooks/useCAT20BalanceCardLogic.ts +35 -0
  57. package/src/ui-hooks/useCAT20ListLogic.ts +83 -0
  58. package/src/ui-hooks/useCAT20TokenScreenLogic.ts +122 -0
  59. package/src/ui-hooks/useCAT721ListLogic.ts +68 -0
  60. package/src/ui-hooks/useCAT721NFTScreenLogic.ts +37 -0
  61. package/src/ui-hooks/useCreatePasswordScreenLogic.ts +92 -0
  62. package/src/ui-hooks/useCreateWalletLogicImportWordsStep.ts +299 -0
  63. package/src/ui-hooks/useEditAccountNameScreenLogic.ts +71 -0
  64. package/src/ui-hooks/useEditContactScreenLogic.ts +162 -0
  65. package/src/ui-hooks/useEditWalletNameScreenLogic.ts +58 -0
  66. package/src/ui-hooks/useExportMnemonicsScreenLogic.ts +75 -0
  67. package/src/ui-hooks/useExportPrivateKeyScreenLogic.ts +64 -0
  68. package/src/ui-hooks/useFeeRateBarLogic.ts +303 -0
  69. package/src/ui-hooks/useInfiniteList.ts +85 -0
  70. package/src/ui-hooks/useInscriptionListLogic.ts +68 -0
  71. package/src/ui-hooks/useLockTimePageLogic.ts +43 -0
  72. package/src/ui-hooks/useNotificationsLogic.ts +115 -0
  73. package/src/ui-hooks/useOrdinalsInscriptionScreenLogic.ts +130 -0
  74. package/src/ui-hooks/useRunesBalanceCardLogic.ts +44 -0
  75. package/src/ui-hooks/useRunesListLogic.ts +74 -0
  76. package/src/ui-hooks/useRunesTokenScreenLogic.ts +149 -0
  77. package/src/ui-hooks/useSecurityCardLogic.ts +0 -0
  78. package/src/ui-hooks/useSendAlkanesNFTScreenLogic.ts +138 -0
  79. package/src/ui-hooks/useSendAlkanesScreenLogic.ts +192 -0
  80. package/src/ui-hooks/useSendCAT20ScreenLogic.ts +297 -0
  81. package/src/ui-hooks/useSendCAT721ScreenLogic.ts +205 -0
  82. package/src/ui-hooks/useSendOrdinalsInscriptionScreenLogic.ts +137 -0
  83. package/src/ui-hooks/useSendRunesScreenLogic.ts +172 -0
  84. package/src/ui-hooks/useSettingsTabScreenLogic.ts +211 -0
  85. package/src/ui-hooks/useSignMessageLogic.ts +302 -0
  86. package/src/ui-hooks/useSignPsbtLogic.ts +517 -0
  87. package/src/ui-hooks/useSplitOrdinalsInscriptionScreenLogic.ts +95 -0
  88. package/src/ui-hooks/useTxConfirmScreenLogic.ts +47 -0
  89. package/src/ui-hooks/useTxCreateScreenLogic.ts +161 -0
  90. package/src/ui-hooks/useTxFailScreenLogic.ts +26 -0
  91. package/src/ui-hooks/useTxSuccessScreenLogic.ts +33 -0
  92. package/src/updater/accounts.ts +11 -11
  93. package/src/utils/bitcoin-utils.ts +17 -8
  94. package/src/utils/eventBus.ts +2 -1
  95. package/src/utils/password-utils.ts +78 -0
  96. package/src/utils/ui-utils.ts +28 -0
  97. package/src/hooks/approval.ts +0 -72
  98. package/src/hooks/i18n.ts +0 -53
  99. package/src/utils/i18n.ts +0 -41
@@ -0,0 +1,68 @@
1
+ import { AlkanesCollection } from '@unisat/wallet-shared'
2
+
3
+ import { useEffect, useRef } from 'react'
4
+ import {
5
+ AlkanesAssetTabKey,
6
+ getSupportedAssets,
7
+ useAlkanesAssetTabKey,
8
+ useChainType,
9
+ useCurrentAccount,
10
+ useNavigation,
11
+ useWallet,
12
+ useWallTabFocusRefresh,
13
+ } from '..'
14
+ import { useInfiniteList } from './useInfiniteList'
15
+
16
+ export function useAlkanesCollectionListLogic() {
17
+ const nav = useNavigation()
18
+ const wallet = useWallet()
19
+ const currentAccount = useCurrentAccount()
20
+ const chainType = useChainType()
21
+
22
+ const {
23
+ data: items,
24
+ total,
25
+ loading,
26
+ hasMore,
27
+ onRefresh,
28
+ onLoadMore,
29
+ } = useInfiniteList<AlkanesCollection>({
30
+ fetcher: async (page, pageSize) => {
31
+ const supportedAssets = getSupportedAssets(chainType, currentAccount.address)
32
+ if (!supportedAssets.assets.alkanes || currentAccount.address === '') {
33
+ return { list: [], total: 0 }
34
+ }
35
+ const { list, total } = await wallet.getAlkanesCollectionList(
36
+ currentAccount.address,
37
+ page,
38
+ pageSize
39
+ )
40
+
41
+ return { list, total }
42
+ },
43
+ dependencies: [currentAccount.address, chainType],
44
+ })
45
+
46
+ const tabKey = useAlkanesAssetTabKey()
47
+ const isFocus = tabKey === AlkanesAssetTabKey.COLLECTION
48
+ const lastRefreshTimeRef = useRef<number>(0)
49
+ const walletTabFocusRefresh = useWallTabFocusRefresh()
50
+ useEffect(() => {
51
+ if (!isFocus) return
52
+
53
+ // already refreshed → do nothing
54
+ const alreadyRefreshed = lastRefreshTimeRef.current === walletTabFocusRefresh
55
+ if (alreadyRefreshed) return
56
+
57
+ onRefresh()
58
+
59
+ // mark refreshed
60
+ lastRefreshTimeRef.current = walletTabFocusRefresh
61
+ }, [walletTabFocusRefresh, isFocus])
62
+
63
+ const onClickItem = (item: AlkanesCollection) => {
64
+ nav.navigate('AlkanesCollectionScreen', { collectionId: item.alkaneid })
65
+ }
66
+
67
+ return { items, total, loading, hasMore, onRefresh, onLoadMore, onClickItem }
68
+ }
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ import { AlkanesBalance, TickPriceItem } from '@unisat/wallet-shared'
4
+
5
+ import {
6
+ AlkanesAssetTabKey,
7
+ getSupportedAssets,
8
+ useAlkanesAssetTabKey,
9
+ useChainType,
10
+ useCurrentAccount,
11
+ useNavigation,
12
+ useWallet,
13
+ useWallTabFocusRefresh,
14
+ } from '..'
15
+ import { useInfiniteList } from './useInfiniteList'
16
+
17
+ export function useAlkanesListLogic() {
18
+ const nav = useNavigation()
19
+ const wallet = useWallet()
20
+ const currentAccount = useCurrentAccount()
21
+ const chainType = useChainType()
22
+ const [priceMap, setPriceMap] = useState<{ [key: string]: TickPriceItem }>({})
23
+
24
+ const {
25
+ data: items,
26
+ total,
27
+ loading,
28
+ hasMore,
29
+ onRefresh,
30
+ onLoadMore,
31
+ } = useInfiniteList<AlkanesBalance>({
32
+ fetcher: async (page, pageSize) => {
33
+ const supportedAssets = getSupportedAssets(chainType, currentAccount.address)
34
+ if (!supportedAssets.assets.alkanes || currentAccount.address === '') {
35
+ return { list: [], total: 0 }
36
+ }
37
+
38
+ const { list, total } = await wallet.getAlkanesList(currentAccount.address, page, pageSize)
39
+ if (list.length > 0) {
40
+ wallet.getAlkanesPrice(list.map(item => item.alkaneid)).then(setPriceMap)
41
+ }
42
+ return { list, total }
43
+ },
44
+ dependencies: [currentAccount.address, chainType],
45
+ })
46
+
47
+ const tabKey = useAlkanesAssetTabKey()
48
+ const isFocus = tabKey === AlkanesAssetTabKey.TOKEN
49
+ const lastRefreshTimeRef = useRef<number>(0)
50
+ const walletTabFocusRefresh = useWallTabFocusRefresh()
51
+ useEffect(() => {
52
+ if (!isFocus) return
53
+
54
+ // already refreshed → do nothing
55
+ const alreadyRefreshed = lastRefreshTimeRef.current === walletTabFocusRefresh
56
+ if (alreadyRefreshed) return
57
+
58
+ onRefresh()
59
+
60
+ // mark refreshed
61
+ lastRefreshTimeRef.current = walletTabFocusRefresh
62
+ }, [walletTabFocusRefresh, isFocus])
63
+
64
+ const onClickItem = (item: AlkanesBalance) => {
65
+ nav.navigate('AlkanesTokenScreen', { alkaneid: item.alkaneid })
66
+ }
67
+
68
+ return { items, total, loading, hasMore, onRefresh, onLoadMore, onClickItem, priceMap }
69
+ }
@@ -0,0 +1,42 @@
1
+ import { AlkanesInfo } from '@unisat/wallet-shared'
2
+
3
+ import { getSupportedAssets, useChainType, useCurrentAccount, useNavigation, useWallet } from '..'
4
+ import { useInfiniteList } from './useInfiniteList'
5
+
6
+ export function useAlkanesNFTListLogic(collectionId: string) {
7
+ const nav = useNavigation()
8
+ const wallet = useWallet()
9
+ const currentAccount = useCurrentAccount()
10
+ const chainType = useChainType()
11
+
12
+ const {
13
+ data: items,
14
+ total,
15
+ loading,
16
+ hasMore,
17
+ onRefresh,
18
+ onLoadMore,
19
+ } = useInfiniteList<AlkanesInfo>({
20
+ fetcher: async (page, pageSize) => {
21
+ const supportedAssets = getSupportedAssets(chainType, currentAccount.address)
22
+ if (!supportedAssets.assets.alkanes || currentAccount.address === '') {
23
+ return { list: [], total: 0 }
24
+ }
25
+ const { list, total } = await wallet.getAlkanesCollectionItems(
26
+ currentAccount.address,
27
+ collectionId,
28
+ page,
29
+ pageSize
30
+ )
31
+
32
+ return { list, total }
33
+ },
34
+ dependencies: [currentAccount.address, collectionId, chainType],
35
+ })
36
+
37
+ const onClickItem = (item: AlkanesInfo) => {
38
+ nav.navigate('AlkanesNFTScreen', { alkanesInfo: item })
39
+ }
40
+
41
+ return { items, total, loading, hasMore, onRefresh, onLoadMore, onClickItem }
42
+ }
@@ -0,0 +1,45 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { useI18n, useNavigation, useWallet } from 'src/context'
3
+ import { useResetTxState } from 'src/hooks'
4
+
5
+ export function useAlkanesNFTScreenLogic() {
6
+ const nav = useNavigation()
7
+ const { alkanesInfo } = nav.getRouteState<'AlkanesNFTScreen'>()
8
+
9
+ const { t } = useI18n()
10
+
11
+ const resetTxState = useResetTxState()
12
+
13
+ const [availableUtxo, setAvailableUtxo] = useState(0)
14
+ const wallet = useWallet()
15
+
16
+ useEffect(() => {
17
+ const fetchData = async () => {
18
+ const utxos = await wallet.getAssetUtxosAlkanes(alkanesInfo.alkaneid)
19
+ setAvailableUtxo(utxos.length)
20
+ }
21
+ fetchData()
22
+ }, [wallet])
23
+
24
+ const onClickBack = () => {
25
+ nav.goBack()
26
+ }
27
+
28
+ const onClickSend = () => {
29
+ resetTxState()
30
+ nav.navigate('SendAlkanesNFTScreen', {
31
+ alkanesInfo,
32
+ })
33
+ }
34
+
35
+ const disabledSend = availableUtxo <= 0
36
+
37
+ return {
38
+ alkanesInfo,
39
+ t,
40
+ availableUtxo,
41
+ onClickBack,
42
+ onClickSend,
43
+ disabledSend,
44
+ }
45
+ }
@@ -0,0 +1,130 @@
1
+ import { useEffect, useMemo, useState } from 'react'
2
+
3
+ import { AddressAlkanesTokenSummary } from '@unisat/wallet-shared'
4
+ import {
5
+ useAlkanesIconInfo,
6
+ useCurrentAccount,
7
+ useI18n,
8
+ useNavigation,
9
+ useResetTxState,
10
+ useTools,
11
+ useWallet,
12
+ } from '..'
13
+
14
+ export function useAlkanesTokenScreenLogic() {
15
+ const nav = useNavigation()
16
+ const { alkaneid } = nav.getRouteState<'AlkanesTokenScreen'>()
17
+ const [tokenSummary, setTokenSummary] = useState<AddressAlkanesTokenSummary>({
18
+ tokenBalance: {
19
+ alkaneid: '',
20
+ name: '',
21
+ amount: '',
22
+ symbol: '',
23
+ divisibility: 0,
24
+ available: '',
25
+ },
26
+ tokenInfo: {
27
+ alkaneid: '',
28
+ name: '',
29
+ symbol: '',
30
+ totalSupply: '10000000000000',
31
+ maxSupply: '10000000000000',
32
+ cap: 0,
33
+ mintable: false,
34
+ perMint: '0',
35
+ minted: 0,
36
+ holders: 0,
37
+ aligned: true,
38
+ nftData: {
39
+ collectionId: '',
40
+ },
41
+ logo: '',
42
+ },
43
+ tradeUrl: '',
44
+ mintUrl: '',
45
+ })
46
+
47
+ const wallet = useWallet()
48
+
49
+ const account = useCurrentAccount()
50
+
51
+ const [loading, setLoading] = useState(true)
52
+
53
+ const [warning, setWarning] = useState(false)
54
+
55
+ const { t } = useI18n()
56
+
57
+ useEffect(() => {
58
+ wallet.getAddressAlkanesTokenSummary(account.address, alkaneid, false).then(tokenSummary => {
59
+ setTokenSummary(tokenSummary)
60
+ setLoading(false)
61
+ })
62
+ }, [])
63
+
64
+ const resetTxState = useResetTxState()
65
+
66
+ const enableMint = useMemo(() => {
67
+ return tokenSummary.mintUrl && tokenSummary.mintUrl.trim() !== ''
68
+ }, [tokenSummary.mintUrl])
69
+
70
+ const enableTransfer = useMemo(() => {
71
+ let enable = false
72
+ if (tokenSummary.tokenBalance.amount !== '0') {
73
+ enable = true
74
+ }
75
+ return enable
76
+ }, [tokenSummary])
77
+
78
+ const tools = useTools()
79
+
80
+ const enableTrade = useMemo(() => {
81
+ return tokenSummary.tradeUrl && tokenSummary.tradeUrl.trim() !== ''
82
+ }, [tokenSummary.tradeUrl])
83
+
84
+ const onClickMint = () => {
85
+ if (tokenSummary.mintUrl) {
86
+ nav.navToUrl(tokenSummary.mintUrl)
87
+ }
88
+ }
89
+
90
+ const onClickSend = () => {
91
+ if (tokenSummary.tokenInfo?.aligned === false) {
92
+ // tools.toastError(t('important_to_not_transfer_this_token'));
93
+ setWarning(true)
94
+ return
95
+ }
96
+
97
+ resetTxState()
98
+ nav.navigate('SendAlkanesScreen', {
99
+ tokenBalance: tokenSummary.tokenBalance,
100
+ tokenInfo: tokenSummary.tokenInfo,
101
+ })
102
+ }
103
+
104
+ const onClickTrade = () => {
105
+ if (tokenSummary.tradeUrl) {
106
+ nav.navToUrl(tokenSummary.tradeUrl)
107
+ }
108
+ }
109
+
110
+ const iconInfo = useAlkanesIconInfo(
111
+ tokenSummary.tokenBalance.name,
112
+ tokenSummary.tokenBalance.alkaneid
113
+ )
114
+
115
+ return {
116
+ tokenSummary,
117
+ loading,
118
+ enableMint,
119
+ enableTransfer,
120
+ enableTrade,
121
+ warning,
122
+ setWarning,
123
+ t,
124
+ tools,
125
+ onClickMint,
126
+ onClickSend,
127
+ onClickTrade,
128
+ iconInfo,
129
+ }
130
+ }
@@ -0,0 +1,80 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+
3
+ export interface AmountInputProps {}
4
+
5
+ export function useAmountInputLogic(props: {
6
+ value?: string
7
+ runesDecimal?: number
8
+ disableDecimal?: boolean
9
+ enableBrc20Decimal?: boolean
10
+ onAmountInputChange?: (amount: string) => void
11
+
12
+ min?: number
13
+ step?: number
14
+ }) {
15
+ const { disableDecimal, enableBrc20Decimal, runesDecimal, onAmountInputChange, min, step } = props
16
+
17
+ const [inputValue, setInputValue] = useState(props.value || '')
18
+
19
+ useEffect(() => {
20
+ onAmountInputChange(inputValue)
21
+ }, [inputValue])
22
+
23
+ const propValueRef = useRef(props.value)
24
+ useEffect(() => {
25
+ if (props.value !== propValueRef.current) {
26
+ propValueRef.current = props.value
27
+ setInputValue(props.value || '')
28
+ }
29
+ }, [props.value])
30
+
31
+ const handleInputAmount = useCallback(
32
+ (e: { target: { value: string } } | string) => {
33
+ const raw = typeof e === 'string' ? e : e.target.value
34
+
35
+ let regex: RegExp
36
+ if (disableDecimal) {
37
+ regex = /^[1-9]\d*$/
38
+ } else if (enableBrc20Decimal) {
39
+ regex = /^(0(\.\d{0,18})?|[1-9]\d*\.?\d{0,18})$/
40
+ } else if (runesDecimal !== undefined) {
41
+ regex = new RegExp(`^(0(\\.\\d{0,${runesDecimal}})?|[1-9]\\d*\\.?\\d{0,${runesDecimal}})$`)
42
+ } else {
43
+ regex = /^(0(\.\d{0,8})?|[1-9]\d*\.?\d{0,8})$/
44
+ }
45
+
46
+ if (regex.test(raw) || raw === '') {
47
+ setInputValue(raw)
48
+ }
49
+ },
50
+ [disableDecimal, enableBrc20Decimal, runesDecimal]
51
+ )
52
+
53
+ const handleStepUp = useCallback(() => {
54
+ setInputValue(prev => {
55
+ const currentVal = parseFloat(prev) || 0
56
+ const decimal = runesDecimal ?? 2
57
+ return (currentVal + step).toFixed(decimal)
58
+ })
59
+ }, [step, runesDecimal])
60
+
61
+ const handleStepDown = useCallback(() => {
62
+ setInputValue(prev => {
63
+ const currentVal = parseFloat(prev) || 0
64
+ const decimal = runesDecimal ?? 2
65
+ return Math.max(min, currentVal - step).toFixed(decimal)
66
+ })
67
+ }, [step, min, runesDecimal])
68
+
69
+ const handleReset = useCallback(() => {
70
+ setInputValue('')
71
+ }, [])
72
+
73
+ return {
74
+ handleInputAmount,
75
+ handleStepUp,
76
+ handleStepDown,
77
+ handleReset,
78
+ inputValue,
79
+ }
80
+ }
@@ -0,0 +1,91 @@
1
+ import { Announcement, AnnouncementLinkType } from '@unisat/wallet-shared'
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3
+ import { useStorage, useWallet } from 'src/context'
4
+
5
+ const AUTO_PLAY_INTERVAL = 5000
6
+
7
+ export function useAnnouncementCardLogic() {
8
+ const wallet = useWallet()
9
+ const storage = useStorage()
10
+
11
+ const [activeIndex, setActiveIndex] = useState(0)
12
+ const [dismissedIds, setDismissedIds] = useState<string[]>([])
13
+ const [announcements, setAnnouncements] = useState<Announcement[]>([])
14
+ const [loading, setLoading] = useState(false)
15
+ const [dismissedLoaded, setDismissedLoaded] = useState(false)
16
+ const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
17
+
18
+ useEffect(() => {
19
+ const init = async () => {
20
+ try {
21
+ await loadDismissedIds()
22
+ await fetchAnnouncements()
23
+ } finally {
24
+ setDismissedLoaded(true)
25
+ }
26
+ }
27
+ init()
28
+ }, [])
29
+
30
+ const fetchAnnouncements = async () => {
31
+ try {
32
+ const response = await wallet.getAnnouncements(0, 10)
33
+ setAnnouncements(Array.isArray(response?.list) ? response.list : [])
34
+ } catch {
35
+ setAnnouncements([])
36
+ }
37
+ }
38
+
39
+ const loadDismissedIds = async () => {
40
+ const saved = await storage.getAnnouncementDismissedIds()
41
+ setDismissedIds(saved)
42
+ }
43
+
44
+ const validAnnouncements = useMemo(
45
+ () =>
46
+ announcements.filter(a => {
47
+ const now = Date.now()
48
+ return now >= a.startTime && now <= a.endTime && !dismissedIds.includes(a.id)
49
+ }),
50
+ [announcements, dismissedIds],
51
+ )
52
+
53
+ // Auto-play: hook is the single timing authority
54
+ useEffect(() => {
55
+ if (validAnnouncements.length <= 1) return undefined
56
+ timerRef.current = setInterval(() => {
57
+ setActiveIndex(prev => (prev + 1) % validAnnouncements.length)
58
+ }, AUTO_PLAY_INTERVAL)
59
+ return () => {
60
+ if (timerRef.current) clearInterval(timerRef.current)
61
+ }
62
+ }, [validAnnouncements.length])
63
+
64
+ const handleDotClick = useCallback((index: number) => {
65
+ setActiveIndex(index)
66
+ }, [])
67
+
68
+ const handleDismissAll = useCallback(async () => {
69
+ const ids = validAnnouncements.map(a => a.id)
70
+ if (ids.length === 0) return
71
+ const newIds = [...dismissedIds, ...ids]
72
+ setDismissedIds(newIds)
73
+ await storage.setAnnouncementDismissedIds(newIds)
74
+ }, [validAnnouncements, dismissedIds, storage])
75
+
76
+ const isLinkable = useCallback((announcement: Announcement) => {
77
+ return announcement.linkType !== AnnouncementLinkType.NONE && !!announcement.link
78
+ }, [])
79
+
80
+ const current = validAnnouncements[activeIndex] ?? validAnnouncements[0]
81
+
82
+ return {
83
+ loading,
84
+ validAnnouncements,
85
+ current,
86
+ activeIndex,
87
+ handleDotClick,
88
+ handleDismissAll,
89
+ isLinkable,
90
+ }
91
+ }
@@ -0,0 +1,115 @@
1
+ import { AddressTokenSummary, TickPriceItem, TokenBalance } from '@unisat/wallet-shared'
2
+ import BigNumber from 'bignumber.js'
3
+ import { useMemo, useState } from 'react'
4
+ import { useI18n } from 'src/context'
5
+ import { useBRC20IconInfo, useChain, useCurrentAccount } from 'src/hooks'
6
+
7
+ export interface BRC20BalanceCardProps {
8
+ tokenBalance: TokenBalance
9
+ onClick?: () => void
10
+ price?: TickPriceItem
11
+ }
12
+ export function useBRC20BalanceCardLogic(props: BRC20BalanceCardProps) {
13
+ const {
14
+ price,
15
+ tokenBalance: {
16
+ ticker,
17
+ overallBalance,
18
+ transferableBalance,
19
+ selfMint,
20
+ displayName,
21
+ tag,
22
+ swapBalance,
23
+ progBalance,
24
+ },
25
+ onClick,
26
+ } = props
27
+
28
+ const account = useCurrentAccount()
29
+ const [tokenSummary, setTokenSummary] = useState<AddressTokenSummary>()
30
+
31
+ const { t } = useI18n()
32
+
33
+ const deploy_count = tokenSummary ? (tokenSummary.tokenInfo.holder == account.address ? 1 : 0) : 0
34
+ let _names: string[] = []
35
+ const _amounts: string[] = []
36
+ if (deploy_count > 0) {
37
+ _names.push('Deploy')
38
+ _amounts.push('')
39
+ }
40
+ if (tokenSummary) {
41
+ tokenSummary.transferableList.forEach(v => {
42
+ _names.push('Transfer')
43
+ _amounts.push(v.amount)
44
+ })
45
+ }
46
+
47
+ for (let i = 0; i < _names.length; i++) {
48
+ if (i == 3) {
49
+ if (_names.length > 4) {
50
+ if (deploy_count > 0) {
51
+ _names[i] = `${_names.length - 3}+`
52
+ } else {
53
+ _names[i] = `${_names.length - 2}+`
54
+ }
55
+ _amounts[i] = ''
56
+ }
57
+ break
58
+ }
59
+ }
60
+ _names = _names.splice(0, 4)
61
+
62
+ const onSwapBalance = swapBalance
63
+ const onProgBalance = progBalance
64
+ const inWalletBalance = overallBalance
65
+
66
+ const totalBalance = useMemo(() => {
67
+ return new BigNumber(inWalletBalance)
68
+ .plus(new BigNumber(onSwapBalance || 0))
69
+ .plus(new BigNumber(onProgBalance || 0))
70
+ .toString()
71
+ }, [inWalletBalance, onSwapBalance, onProgBalance])
72
+
73
+ let hasOutWalletBalance = false
74
+ if (onSwapBalance && onSwapBalance !== '0') {
75
+ hasOutWalletBalance = true
76
+ }
77
+ if (onProgBalance && onProgBalance !== '0') {
78
+ hasOutWalletBalance = true
79
+ }
80
+
81
+ // icon
82
+ const iconInfo = useBRC20IconInfo(ticker)
83
+
84
+ // price
85
+ const chain = useChain()
86
+ const showPrice = chain.showPrice
87
+
88
+ return {
89
+ // info
90
+ ticker,
91
+ displayName,
92
+ selfMint,
93
+ tag,
94
+
95
+ // icon
96
+ iconInfo,
97
+
98
+ // balance
99
+ totalBalance,
100
+ hasOutWalletBalance,
101
+ onProgBalance,
102
+ inWalletBalance,
103
+ onSwapBalance,
104
+
105
+ // price
106
+ price,
107
+ showPrice,
108
+
109
+ // click
110
+ onClick,
111
+
112
+ // others
113
+ t,
114
+ }
115
+ }