diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4a38b3db..ea3cca2a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2046,9 +2046,9 @@ SPEC CHECKSUMS: Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e VisionCamera: cb84d0d8485b3e67c91b62931d3aa88f49747c92 - Yoga: 950bbfd7e6f04790fdb51149ed51df41f329fcc8 + Yoga: 2246eea72aaf1b816a68a35e6e4b74563653ae09 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 PODFILE CHECKSUM: af8e0f0904c0f9cc43a95eef4e1feb504870ee6e -COCOAPODS: 1.16.1 +COCOAPODS: 1.15.2 diff --git a/src/app/services/HotspotService/pages/ClaimTokensPage.tsx b/src/app/services/HotspotService/pages/ClaimTokensPage.tsx index d8d34640..8d497536 100644 --- a/src/app/services/HotspotService/pages/ClaimTokensPage.tsx +++ b/src/app/services/HotspotService/pages/ClaimTokensPage.tsx @@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next' import { Image, RefreshControl } from 'react-native' import MobileIcon from '@assets/svgs/mobileIconNew.svg' import IotIcon from '@assets/svgs/iotIconNew.svg' +import HntIcon from '@assets/svgs/hnt.svg' import TouchableContainer from '@components/TouchableContainer' import BalanceText from '@components/BalanceText' import useHotspots from '@hooks/useHotspots' @@ -15,6 +16,7 @@ import { toNumber } from '@helium/spl-utils' import { MOBILE_LAZY_KEY, IOT_LAZY_KEY, + HNT_LAZY_KEY, MIN_BALANCE_THRESHOLD, } from '@utils/constants' import useSubmitTxn from '@hooks/useSubmitTxn' @@ -44,6 +46,7 @@ const ClaimTokensPage = () => { const { pendingIotRewards, + pendingHntRewards, pendingMobileRewards, hotspotsWithMeta, totalHotspots, @@ -62,6 +65,11 @@ const ClaimTokensPage = () => { return toNumber(pendingIotRewards, 6) }, [pendingIotRewards]) + const totalPendingHnt = useMemo(() => { + if (!pendingHntRewards) return 0 + return toNumber(pendingHntRewards, 6) + }, [pendingHntRewards]) + const totalPendingMobile = useMemo(() => { if (!pendingMobileRewards) return 0 return toNumber(pendingMobileRewards, 6) @@ -83,15 +91,23 @@ const ClaimTokensPage = () => { return ( claiming || !hasEnoughSol || - (totalPendingIot === 0 && totalPendingMobile === 0) + (totalPendingIot === 0 && + totalPendingMobile === 0 && + totalPendingHnt === 0) ) - }, [claiming, hasEnoughSol, totalPendingIot, totalPendingMobile]) + }, [ + claiming, + hasEnoughSol, + totalPendingIot, + totalPendingMobile, + totalPendingHnt, + ]) const onClaim = useCallback(async () => { try { const claim = async () => { await submitClaimAllRewards( - [IOT_LAZY_KEY, MOBILE_LAZY_KEY], + [IOT_LAZY_KEY, MOBILE_LAZY_KEY, HNT_LAZY_KEY], hotspotsWithMeta, totalHotspots, ) @@ -154,25 +170,48 @@ const ClaimTokensPage = () => { gap="1" marginTop="4xl" > - - - - - - - MOBILE - + {totalPendingMobile > 0 && ( + + + + + + + MOBILE + + - - + + )} + {totalPendingIot > 0 && ( + + + + + + + IOT + + + + + )} { }} > - + - + - IOT + HNT diff --git a/src/features/hotspots/ChangeRewardsRecipientScreen.tsx b/src/features/hotspots/ChangeRewardsRecipientScreen.tsx index a2bcac50..7d83bca0 100644 --- a/src/features/hotspots/ChangeRewardsRecipientScreen.tsx +++ b/src/features/hotspots/ChangeRewardsRecipientScreen.tsx @@ -26,7 +26,7 @@ import { TouchableWithoutFeedback, } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { IOT_LAZY_KEY, MOBILE_LAZY_KEY } from '@utils/constants' +import { HNT_LAZY_KEY, IOT_LAZY_KEY, MOBILE_LAZY_KEY } from '@utils/constants' import { PublicKey } from '@solana/web3.js' import TouchableOpacityBox from '@components/TouchableOpacityBox' import { useCurrentWallet } from '@hooks/useCurrentWallet' @@ -139,7 +139,7 @@ const ChangeRewardsRecipientScreen = () => { setUpdating(true) try { await submitUpdateRewardsDestination({ - lazyDistributors: [IOT_LAZY_KEY, MOBILE_LAZY_KEY], + lazyDistributors: [IOT_LAZY_KEY, MOBILE_LAZY_KEY, HNT_LAZY_KEY], destination: recipient, assetId: hotspot.id, }) @@ -157,7 +157,7 @@ const ChangeRewardsRecipientScreen = () => { setRemoving(true) try { await submitUpdateRewardsDestination({ - lazyDistributors: [IOT_LAZY_KEY, MOBILE_LAZY_KEY], + lazyDistributors: [IOT_LAZY_KEY, MOBILE_LAZY_KEY, HNT_LAZY_KEY], destination: PublicKey.default.toBase58(), assetId: hotspot.id, }) diff --git a/src/features/hotspots/ClaimAllRewardsScreen.tsx b/src/features/hotspots/ClaimAllRewardsScreen.tsx index 78bc01c5..51a51be0 100644 --- a/src/features/hotspots/ClaimAllRewardsScreen.tsx +++ b/src/features/hotspots/ClaimAllRewardsScreen.tsx @@ -15,6 +15,7 @@ import useSubmitTxn from '@hooks/useSubmitTxn' import { useNavigation } from '@react-navigation/native' import { useModal } from '@config/storage/ModalsProvider' import { + HNT_LAZY_KEY, IOT_LAZY_KEY, MIN_BALANCE_THRESHOLD, MOBILE_LAZY_KEY, @@ -61,7 +62,7 @@ const ClaimAllRewardsScreen = () => { setRedeeming(true) const claim = async () => { await submitClaimAllRewards( - [IOT_LAZY_KEY, MOBILE_LAZY_KEY], + [IOT_LAZY_KEY, MOBILE_LAZY_KEY, HNT_LAZY_KEY], hotspotsWithMeta, totalHotspots, ) diff --git a/src/features/hotspots/ClaimRewardsScreen.tsx b/src/features/hotspots/ClaimRewardsScreen.tsx index 046ac840..20b58ce9 100644 --- a/src/features/hotspots/ClaimRewardsScreen.tsx +++ b/src/features/hotspots/ClaimRewardsScreen.tsx @@ -33,7 +33,8 @@ const ClaimRewardsScreen = () => { const mint = useMemo(() => new PublicKey(hotspot.id), [hotspot.id]) const { submitClaimRewards } = useSubmitTxn() - const { createClaimMobileTx, createClaimIotTx } = useHotspot(mint) + const { createClaimMobileTx, createClaimIotTx, createClaimHntTx } = + useHotspot(mint) const pendingIotRewards = useMemo( () => @@ -51,6 +52,14 @@ const ClaimRewardsScreen = () => { [hotspot], ) + const pendingHntRewards = useMemo( + () => + hotspot && + hotspot.pendingRewards && + new BN(hotspot.pendingRewards[Mints.HNT]), + [hotspot], + ) + const title = useMemo(() => { return t('collectablesScreen.hotspots.claimRewards') }, [t]) @@ -73,6 +82,11 @@ const ClaimRewardsScreen = () => { pendingMobileRewards && !pendingMobileRewards.eq(new BN(0)) ? await createClaimMobileTx() : undefined + const claimHntTx = + pendingHntRewards && !pendingHntRewards.eq(new BN(0)) + ? await createClaimHntTx() + : undefined + const transactions: VersionedTransaction[] = [] if (claimIotTx && pendingIotRewards) { @@ -83,6 +97,10 @@ const ClaimRewardsScreen = () => { transactions.push(claimMobileTx) } + if (claimHntTx && pendingHntRewards) { + transactions.push(claimHntTx) + } + if (transactions.length > 0) { await submitClaimRewards(transactions) nav.push('ClaimingRewardsScreen') diff --git a/src/features/hotspots/HotspotMapHotspotDetails.tsx b/src/features/hotspots/HotspotMapHotspotDetails.tsx index a7227059..9f63d802 100644 --- a/src/features/hotspots/HotspotMapHotspotDetails.tsx +++ b/src/features/hotspots/HotspotMapHotspotDetails.tsx @@ -1,6 +1,7 @@ import CopyAddress from '@assets/svgs/copyAddress.svg' import Hex from '@assets/svgs/hex.svg' import IotSymbol from '@assets/svgs/iotSymbol.svg' +import HntSymbol from '@assets/svgs/hntSymbol.svg' import MobileSymbol from '@assets/svgs/mobileSymbol.svg' import { ReAnimatedBlurBox } from '@components/AnimatedBox' import Box from '@components/Box' @@ -13,7 +14,7 @@ import TouchableOpacityBox from '@components/TouchableOpacityBox' import { makerApprovalKey } from '@helium/helium-entity-manager-sdk' import { useMint } from '@helium/helium-react-hooks' import { NetworkType } from '@helium/onboarding' -import { IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' +import { HNT_MINT, IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' import useCopyText from '@hooks/useCopyText' import { useCurrentWallet } from '@hooks/useCurrentWallet' import { useEntityKey } from '@hooks/useEntityKey' @@ -161,6 +162,7 @@ export const HotspotMapHotspotDetails = ({ const streetAddress = useHotspotAddress(hotspotWithMeta) const { info: iotMint } = useMint(IOT_MINT) const { info: mobileMint } = useMint(MOBILE_MINT) + const { info: hntMint } = useMint(HNT_MINT) // Use entity key from kta since it's a buffer const iotInfoAcc = useIotInfo(kta?.entityKey) const mobileInfoAcc = useMobileInfo(kta?.entityKey) @@ -239,6 +241,21 @@ export const HotspotMapHotspotDetails = ({ return formatLargeNumber(new BigNumber(num)) }, [hotspotWithMeta, mobileMint]) + const pendingHntRewards = useMemo( + () => + hotspotWithMeta?.pendingRewards && + new BN(hotspotWithMeta?.pendingRewards[Mints.HNT]), + [hotspotWithMeta], + ) + const pendingHntRewardsString = useMemo(() => { + if (!hotspotWithMeta?.pendingRewards) return + const num = toNumber( + new BN(hotspotWithMeta?.pendingRewards[Mints.HNT]), + hntMint?.decimals || 6, + ) + return formatLargeNumber(new BigNumber(num)) + }, [hotspotWithMeta, hntMint]) + const hasIotRewards = useMemo( () => pendingIotRewards && pendingIotRewards.gt(new BN(0)), [pendingIotRewards], @@ -247,10 +264,14 @@ export const HotspotMapHotspotDetails = ({ () => pendingMobileRewards && pendingMobileRewards.gt(new BN(0)), [pendingMobileRewards], ) + const hasHntRewards = useMemo( + () => pendingHntRewards && pendingHntRewards.gt(new BN(0)), + [pendingHntRewards], + ) const hasRewards = useMemo( - () => hasIotRewards || hasMobileRewards, - [hasIotRewards, hasMobileRewards], + () => hasIotRewards || hasMobileRewards || hasHntRewards, + [hasIotRewards, hasMobileRewards, hasHntRewards], ) const mobileRecipient = useMemo( @@ -263,6 +284,11 @@ export const HotspotMapHotspotDetails = ({ [hotspotWithMeta], ) + const hntRecipient = useMemo( + () => hotspotWithMeta?.rewardRecipients?.[Mints.HNT], + [hotspotWithMeta], + ) + const hasIotRecipient = useMemo( () => iotRecipient?.destination && @@ -272,6 +298,15 @@ export const HotspotMapHotspotDetails = ({ [iotRecipient, wallet], ) + const hasHntRecipient = useMemo( + () => + hntRecipient?.destination && + wallet && + !new PublicKey(hntRecipient.destination).equals(wallet) && + !new PublicKey(hntRecipient.destination).equals(PublicKey.default), + [hntRecipient, wallet], + ) + const hasMobileRecipient = useMemo( () => mobileRecipient?.destination && @@ -282,8 +317,8 @@ export const HotspotMapHotspotDetails = ({ ) const hasRecipientSet = useMemo( - () => hasIotRecipient || hasMobileRecipient, - [hasIotRecipient, hasMobileRecipient], + () => hasIotRecipient || hasMobileRecipient || hasHntRecipient, + [hasIotRecipient, hasMobileRecipient, hasHntRecipient], ) const isLoading = useMemo( @@ -686,6 +721,31 @@ export const HotspotMapHotspotDetails = ({ )} + {!!hasHntRewards && ( + + + + {pendingHntRewardsString} + + + )} diff --git a/src/features/hotspots/HotspotPage.tsx b/src/features/hotspots/HotspotPage.tsx index b8d4bbe9..c2f2294c 100644 --- a/src/features/hotspots/HotspotPage.tsx +++ b/src/features/hotspots/HotspotPage.tsx @@ -17,7 +17,7 @@ import { Location, MarkerView } from '@rnmapbox/maps' import ImageBox from '@components/ImageBox' import CarotRight from '@assets/svgs/carot-right.svg' import { getDistance } from 'geolib' -import { MOBILE_MINT, toNumber as heliumToNumber } from '@helium/spl-utils' +import { HNT_MINT, MOBILE_MINT, toNumber as heliumToNumber } from '@helium/spl-utils' import { BN } from '@coral-xyz/anchor' import { toNumber } from 'lodash' import MiniMap from '@components/MiniMap' @@ -134,11 +134,11 @@ const HotspotPage = () => { } return ( (heliumToNumber( - new BN(b?.pendingRewards?.[MOBILE_MINT?.toBase58()] ?? 0), + new BN(b?.pendingRewards?.[HNT_MINT?.toBase58()] ?? 0), 6, ) ?? 0) - (heliumToNumber( - new BN(a?.pendingRewards?.[MOBILE_MINT?.toBase58()] ?? 0), + new BN(a?.pendingRewards?.[HNT_MINT?.toBase58()] ?? 0), 6, ) ?? 0) ) diff --git a/src/hooks/useHotspot.tsx b/src/hooks/useHotspot.tsx index 6491fe2b..e5e627e1 100644 --- a/src/hooks/useHotspot.tsx +++ b/src/hooks/useHotspot.tsx @@ -4,7 +4,7 @@ import { PublicKey, VersionedTransaction } from '@solana/web3.js' import { useEffect, useState } from 'react' import { useAsyncCallback } from 'react-async-hook' import { useSolana } from '@features/solana/SolanaProvider' -import { IOT_LAZY_KEY, MOBILE_LAZY_KEY } from '../utils/constants' +import { HNT_LAZY_KEY, IOT_LAZY_KEY, MOBILE_LAZY_KEY } from '../utils/constants' import * as Logger from '../utils/logger' export function useHotspot(mint: PublicKey): { @@ -12,8 +12,11 @@ export function useHotspot(mint: PublicKey): { mobileRewardsError: Error | undefined createClaimIotTx: () => Promise createClaimMobileTx: () => Promise + createClaimHntTx: () => Promise iotRewardsLoading: boolean mobileRewardsLoading: boolean + hntRewardsLoading: boolean + hntRewardsError: Error | undefined } { const { anchorProvider: provider } = useSolana() @@ -85,6 +88,39 @@ export function useHotspot(mint: PublicKey): { } }) + const { + error: hntRewardsError, + execute: createClaimHntTx, + loading: hntRewardsLoading, + } = useAsyncCallback(async () => { + if (!provider) return + const program = await init(provider) + const { connection } = provider + + if (mint && program && provider) { + const rewards = await client.getCurrentRewards( + // TODO: Fix program type once HPL is upgraded to anchor v0.26 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + program, + HNT_LAZY_KEY, + mint, + ) + + const tx = await client.formTransaction({ + // TODO: Fix program type once HPL is upgraded to anchor v0.26 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + program, + provider, + rewards, + hotspot: mint, + lazyDistributor: HNT_LAZY_KEY, + assetEndpoint: connection.rpcEndpoint, + }) + + return tx + } + }) + useEffect(() => { if (mobileRewardsError) { Logger.error(mobileRewardsError) @@ -95,9 +131,12 @@ export function useHotspot(mint: PublicKey): { return { createClaimMobileTx, createClaimIotTx, + createClaimHntTx, mobileRewardsLoading, iotRewardsLoading, + hntRewardsLoading, iotRewardsError, mobileRewardsError, + hntRewardsError, } } diff --git a/src/hooks/useHotspots.ts b/src/hooks/useHotspots.ts index c606a4ac..4e95c871 100644 --- a/src/hooks/useHotspots.ts +++ b/src/hooks/useHotspots.ts @@ -16,6 +16,7 @@ import { Mints } from '../utils/constants' const useHotspots = (): { totalHotspots: number | undefined pendingIotRewards: BN | undefined + pendingHntRewards: BN | undefined pendingMobileRewards: BN | undefined hotspots: CompressedNFT[] hotspotsWithMeta: HotspotWithPendingRewards[] @@ -191,6 +192,18 @@ const useHotspots = (): { [hotspotsWithMeta], ) + const pendingHntRewards = useMemo( + () => + hotspotsWithMeta?.reduce((acc, hotspot) => { + if (hotspot.pendingRewards) { + return acc.add(new BN(hotspot.pendingRewards[Mints.HNT] || '0')) + } + + return acc + }, new BN(0)), + [hotspotsWithMeta], + ) + const pendingMobileRewards = useMemo( () => hotspotsWithMeta?.reduce((acc, hotspot) => { @@ -210,6 +223,7 @@ const useHotspots = (): { return { totalHotspots, pendingIotRewards, + pendingHntRewards, pendingMobileRewards, loading: false, hotspots: [], @@ -225,6 +239,7 @@ const useHotspots = (): { return { totalHotspots, pendingIotRewards, + pendingHntRewards, pendingMobileRewards, hotspots, hotspotsWithMeta, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2c7b46c9..ddf5d424 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -26,6 +26,7 @@ export const MOBILE_LAZY_KEY = lazyDistributorKey( )[0] export const IOT_LAZY_KEY = lazyDistributorKey(new PublicKey(Mints.IOT))[0] +export const HNT_LAZY_KEY = lazyDistributorKey(new PublicKey(Mints.HNT))[0] export const DAO_KEY = daoKey(HNT_MINT)[0] export const IOT_SUB_DAO_KEY = subDaoKey(IOT_MINT)[0] export const MOBILE_SUB_DAO_KEY = subDaoKey(MOBILE_MINT)[0] diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 88cb94a3..df88f47b 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -127,7 +127,7 @@ import { } from '../types/solana' import { WrappedConnection } from './WrappedConnection' import { solAddressIsValid } from './accountUtils' -import { DAO_KEY, IOT_LAZY_KEY, MOBILE_LAZY_KEY, Mints } from './constants' +import { DAO_KEY, HNT_LAZY_KEY, IOT_LAZY_KEY, MOBILE_LAZY_KEY, Mints } from './constants' import { decimalSeparator, groupSeparator } from './i18n' import * as Logger from './logger' import sleep from './sleep' @@ -1050,6 +1050,14 @@ export const getHotspotPendingRewards = async ( 'b58', true, ) + const hntRewards = await getPendingRewards( + program, + HNT_LAZY_KEY, + dao, + entityKeys, + 'b58', + true, + ) return hotspots.map((hotspot, index) => { const entityKey = entityKeys[index] @@ -1059,6 +1067,7 @@ export const getHotspotPendingRewards = async ( pendingRewards: { [Mints.MOBILE]: mobileRewards[entityKey], [Mints.IOT]: iotRewards[entityKey], + [Mints.HNT]: hntRewards[entityKey], }, } }) @@ -1082,6 +1091,7 @@ export const getHotspotRecipients = async ( (acc: PublicKey[][], asset) => [ [...(acc[0] || []), recipientKey(MOBILE_LAZY_KEY, asset)[0]], [...(acc[1] || []), recipientKey(IOT_LAZY_KEY, asset)[0]], + [...(acc[2] || []), recipientKey(HNT_LAZY_KEY, asset)[0]], ], [], ) @@ -1313,6 +1323,15 @@ export async function annotateWithPendingRewards( true, ) + const hntRewards = await getPendingRewards( + program, + HNT_LAZY_KEY, + dao, + entityKeys, + 'b58', + true, + ) + const rewardRecipients = await getHotspotRecipients(provider, hotspots) const rewardRecipientsById: { [key: string]: { [key: string]: RecipientV0 } @@ -1332,6 +1351,7 @@ export async function annotateWithPendingRewards( pendingRewards: { [Mints.MOBILE]: mobileRewards[entityKey], [Mints.IOT]: iotRewards[entityKey], + [Mints.HNT]: hntRewards[entityKey], }, rewardRecipients: rewardRecipientsById[hotspot.id] || {}, } as HotspotWithPendingRewards