From a2350bda8ced9b83457d8031a19b95817a35a625 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 27 Jul 2023 12:50:32 -0500 Subject: [PATCH 01/23] Add all tokens but not send yet --- src/assets/images/config.svg | 3 + .../account/AccountManageTokenListScreen.tsx | 173 ++++++++++++++++++ src/hooks/useMetaplexMetadata.ts | 61 ++++++ src/hooks/usePublicKey.ts | 10 + src/storage/TokensProvider.tsx | 106 +++++++++++ 5 files changed, 353 insertions(+) create mode 100644 src/assets/images/config.svg create mode 100644 src/features/account/AccountManageTokenListScreen.tsx create mode 100644 src/hooks/useMetaplexMetadata.ts create mode 100644 src/hooks/usePublicKey.ts create mode 100644 src/storage/TokensProvider.tsx diff --git a/src/assets/images/config.svg b/src/assets/images/config.svg new file mode 100644 index 000000000..92c1b8df0 --- /dev/null +++ b/src/assets/images/config.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/features/account/AccountManageTokenListScreen.tsx b/src/features/account/AccountManageTokenListScreen.tsx new file mode 100644 index 000000000..4f6d74f1d --- /dev/null +++ b/src/features/account/AccountManageTokenListScreen.tsx @@ -0,0 +1,173 @@ +import Close from '@assets/images/close.svg' +import Box from '@components/Box' +import IconPressedContainer from '@components/IconPressedContainer' +import SafeAreaBox from '@components/SafeAreaBox' +import Text from '@components/Text' +import TokenIcon from '@components/TokenIcon' +import TouchableContainer from '@components/TouchableContainer' +import { Ticker } from '@helium/currency' +import { useOwnedAmount } from '@helium/helium-react-hooks' +import { DC_MINT, humanReadable } from '@helium/spl-utils' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import CheckBox from '@react-native-community/checkbox' +import { useNavigation } from '@react-navigation/native' +import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { useVisibleTokens } from '@storage/TokensProvider' +import { useColors, useHitSlop } from '@theme/themeHooks' +import { useBalance } from '@utils/Balance' +import BN from 'bn.js' +import React, { memo, useCallback, useMemo } from 'react' +import { FlatList } from 'react-native-gesture-handler' +import { Edge } from 'react-native-safe-area-context' +import { HomeNavigationProp } from '../home/homeTypes' +import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance' +import { getSortValue } from './AccountTokenList' + +const CheckableTokenListItem = ({ + bottomBorder, + mint: token, + checked, + onUpdateTokens, +}: { + bottomBorder: boolean + mint: string + checked: boolean + onUpdateTokens: (_token: PublicKey, _value: boolean) => void +}) => { + const mint = usePublicKey(token) + const { currentAccount } = useAccountStorage() + const wallet = usePublicKey(currentAccount?.solanaAddress) + const { amount, decimals } = useOwnedAmount(wallet, mint) + const { json, symbol } = useMetaplexMetadata(mint) + const balanceToDisplay = useMemo(() => { + return amount ? humanReadable(new BN(amount.toString()), decimals) : '' + }, [amount, decimals]) + const colors = useColors() + + return ( + {}} + flexDirection="row" + minHeight={72} + alignItems="center" + paddingHorizontal="m" + paddingVertical="m" + borderBottomColor="primaryBackground" + borderBottomWidth={bottomBorder ? 0 : 1} + disabled + > + + + + + {`${balanceToDisplay} `} + + + {symbol} + + + {symbol && ( + + )} + + + mint && onUpdateTokens(mint, !checked)} + /> + + + ) +} + +const AccountManageTokenListScreen: React.FC = () => { + const navigation = useNavigation() + const { primaryText } = useColors() + const hitSlop = useHitSlop('l') + const { visibleTokens, setVisibleTokens } = useVisibleTokens() + const { tokenAccounts } = useBalance() + const mints = useMemo(() => { + return tokenAccounts + ?.filter( + (ta) => + ta.balance > 0 && (ta.decimals > 0 || ta.mint === DC_MINT.toBase58()), + ) + .map((ta) => ta.mint) + .sort((a, b) => { + return getSortValue(b) - getSortValue(a) + }) + }, [tokenAccounts]) + + const renderItem = useCallback( + // eslint-disable-next-line react/no-unused-prop-types + ({ index, item: token }: { index: number; item: string }) => { + return ( + + ) + }, + [mints?.length, visibleTokens, setVisibleTokens], + ) + + const keyExtractor = useCallback((item: string) => { + return item + }, []) + const safeEdges = useMemo(() => ['top'] as Edge[], []) + + return ( + + + + + + + + + + + + ) +} + +export default memo(AccountManageTokenListScreen) diff --git a/src/hooks/useMetaplexMetadata.ts b/src/hooks/useMetaplexMetadata.ts new file mode 100644 index 000000000..80e0f1737 --- /dev/null +++ b/src/hooks/useMetaplexMetadata.ts @@ -0,0 +1,61 @@ +import { TypedAccountParser } from '@helium/account-fetch-cache' +import { useAccount } from '@helium/helium-react-hooks' +import { + Metadata, + toMetadata, + toMetadataAccount, +} from '@metaplex-foundation/js' +import { PublicKey } from '@solana/web3.js' +import { useMemo } from 'react' +import { useAsync } from 'react-async-hook' + +const MPL_PID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s') + +const cache: Record = {} +async function getMetadata(uri: string | undefined): Promise { + if (uri) { + if (!cache[uri]) { + const res = await fetch(uri) + const json = await res.json() + cache[uri] = json + } + return cache[uri] + } +} + +export function useMetaplexMetadata(mint: PublicKey | undefined): { + loading: boolean + metadata: Metadata | undefined + json: any | undefined + symbol: string | undefined + name: string | undefined +} { + const metadataAddr = useMemo(() => { + if (mint) { + return PublicKey.findProgramAddressSync( + [Buffer.from('metadata', 'utf-8'), MPL_PID.toBuffer(), mint.toBuffer()], + MPL_PID, + )[0] + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mint?.toBase58()]) + const parser: TypedAccountParser = useMemo(() => { + return (_, account) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return toMetadata(toMetadataAccount(account)) + } + }, []) + const { info: metadataAcc, loading } = useAccount(metadataAddr, parser) + const { result: json, loading: jsonLoading } = useAsync(getMetadata, [ + metadataAcc?.uri, + ]) + + return { + loading: jsonLoading || loading, + json, + metadata: metadataAcc, + symbol: json?.symbol || metadataAcc?.symbol, + name: json?.name || metadataAcc?.name, + } +} diff --git a/src/hooks/usePublicKey.ts b/src/hooks/usePublicKey.ts new file mode 100644 index 000000000..fcdd71c7b --- /dev/null +++ b/src/hooks/usePublicKey.ts @@ -0,0 +1,10 @@ +import { PublicKey } from '@solana/web3.js' +import { useMemo } from 'react' + +export const usePublicKey = (publicKey: string | undefined) => { + return useMemo(() => { + if (publicKey) { + return new PublicKey(publicKey) + } + }, [publicKey]) +} diff --git a/src/storage/TokensProvider.tsx b/src/storage/TokensProvider.tsx new file mode 100644 index 000000000..0fa3bc11c --- /dev/null +++ b/src/storage/TokensProvider.tsx @@ -0,0 +1,106 @@ +import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { PublicKey } from '@solana/web3.js' +import React, { + ReactNode, + createContext, + useCallback, + useContext, + useState, +} from 'react' +import { useAsync } from 'react-async-hook' +import * as Logger from '../utils/logger' +import { useAccountStorage } from './AccountStorageProvider' +import { + CSToken, + restoreVisibleTokens, + updateVisibleTokens, +} from './cloudStorage' + +const DEFAULT_TOKENS = new Set([ + HNT_MINT.toBase58(), + MOBILE_MINT.toBase58(), + IOT_MINT.toBase58(), + DC_MINT.toBase58(), +]) + +const useVisibleTokensHook = () => { + const { currentAccount } = useAccountStorage() + const [visibleTokens, setVisibleTokens] = useState< + Record> + >({ + [currentAccount?.address || '']: DEFAULT_TOKENS, + }) + + useAsync(async () => { + try { + const response = await restoreVisibleTokens() + + if (response) { + setVisibleTokens( + Object.entries(response).reduce((acc, [key, s]) => { + acc[key] = new Set(s) + return acc + }, {} as Record>), + ) + } + } catch { + Logger.error('Restore visible tokens failed') + } + }, []) + + const handleUpdateTokens = useCallback( + (token: PublicKey, value: boolean) => { + if (!currentAccount?.address) return + + const tokens = new Set(visibleTokens[currentAccount.address] || new Set()) + if (value) { + tokens.add(token.toBase58()) + } else { + tokens.delete(token.toBase58()) + } + const newVisibleTokens = { + ...visibleTokens, + [currentAccount.address]: tokens, + } + + updateVisibleTokens( + Object.entries(newVisibleTokens).reduce((acc, [key, s]) => { + acc[key] = Array.from(s) + return acc + }, {} as CSToken), + ) + setVisibleTokens(newVisibleTokens) + }, + [currentAccount?.address, visibleTokens], + ) + + return { + visibleTokens, + setVisibleTokens: handleUpdateTokens, + } +} + +const initialState = { + visibleTokens: {} as Record>, + setVisibleTokens: (_token: PublicKey, _value: boolean) => {}, +} + +const TokensContext = + createContext>(initialState) +const { Provider } = TokensContext + +const TokensProvider = ({ children }: { children: ReactNode }) => { + return {children} +} + +export const useVisibleTokens = () => { + const { currentAccount } = useAccountStorage() + const { visibleTokens, setVisibleTokens } = useContext(TokensContext) + + return { + visibleTokens: visibleTokens[currentAccount?.address || ''] || new Set(), + setVisibleTokens, + } +} + +export default TokensProvider From 8b60b4e486d51c28f472cbc19c834b15a3fd2589 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Fri, 28 Jul 2023 16:50:45 -0500 Subject: [PATCH 02/23] Massive wip --- .yarnrc | 2 +- .../org.eclipse.buildship.core.prefs | 4 +- ios/Podfile.lock | 2 +- package.json | 13 +- src/App.tsx | 57 ++-- src/components/HNTKeyboard.tsx | 245 +++++++----------- src/components/TokenButton.tsx | 43 +-- src/components/TokenIcon.tsx | 28 +- src/components/TokenSelector.tsx | 68 +++-- src/features/account/AccountTokenList.tsx | 106 +++++--- src/features/account/AccountTokenScreen.tsx | 65 ++--- src/features/account/TokenListItem.tsx | 59 +++-- .../account/useSolanaActivityList.tsx | 43 +-- src/features/burn/BurnScreen.tsx | 120 ++++----- src/features/home/HomeNavigator.tsx | 31 ++- src/features/home/homeTypes.ts | 3 +- src/features/payment/PaymentItem.tsx | 59 ++--- src/features/payment/PaymentScreen.tsx | 238 ++++++++--------- src/features/payment/usePaymentsReducer.ts | 120 +++------ src/features/request/RequestScreen.tsx | 162 +++++------- src/features/swaps/SwapItem.tsx | 26 +- src/features/swaps/SwapScreen.tsx | 198 ++++++-------- src/features/swaps/SwappingScreen.tsx | 8 +- src/features/swaps/swapTypes.ts | 2 +- src/hooks/useSubmitTxn.ts | 39 +-- src/locales/en.ts | 1 + src/solana/SolanaProvider.tsx | 36 ++- src/storage/cloudStorage.ts | 14 + src/store/slices/balancesSlice.ts | 26 +- src/store/slices/solanaSlice.ts | 89 +++---- src/types/activity.ts | 6 +- src/types/balance.ts | 1 + src/utils/Balance.tsx | 2 + src/utils/linking.ts | 46 +--- src/utils/solanaUtils.ts | 223 ++++++++-------- src/utils/walletApiV2.ts | 6 +- yarn.lock | 211 +++++++++------ 37 files changed, 1163 insertions(+), 1239 deletions(-) diff --git a/.yarnrc b/.yarnrc index b162bd8bc..6bcc0aaac 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1 +1 @@ ---install.frozen-lockfile true +--install.frozen-lockfile false diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index 98515123a..7cb9c2d7e 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,11 +1,11 @@ -arguments= +arguments=--init-script /var/folders/ck/r25tcygs77n6hv8d515p1w_c0000gn/T/d146c9752a26f79b52047fb6dc6ed385d064e120494f96f08ca63a317c41f94c.gradle --init-script /var/folders/ck/r25tcygs77n6hv8d515p1w_c0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home +java.home=/Library/Java/JavaVirtualMachines/jdk-20.jdk/Contents/Home jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 109a1773f..b761b4cf7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -841,7 +841,7 @@ SPEC CHECKSUMS: BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 34a999fda29036898a09c6a6b728b0b4189e1a44 - Charts: 354f86803d11d9c35de280587fef50d1af063978 + Charts: ce0768268078eee0336f122c3c4ca248e4e204c5 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 EXBarCodeScanner: 8e23fae8d267dbef9f04817833a494200f1fce35 diff --git a/package.json b/package.json index 975a6b999..9d89cb3bd 100644 --- a/package.json +++ b/package.json @@ -39,17 +39,18 @@ "@coral-xyz/anchor": "0.26.0", "@gorhom/bottom-sheet": "4.4.6", "@gorhom/portal": "1.0.14", - "@helium/account-fetch-cache": "^0.2.5", + "@helium/account-fetch-cache": "^0.2.14", + "@helium/account-fetch-cache-hooks": "^0.2.14", "@helium/address": "4.6.2", "@helium/crypto-react-native": "4.8.0", "@helium/currency": "4.11.1", "@helium/currency-utils": "0.1.1", - "@helium/data-credits-sdk": "0.1.2", - "@helium/distributor-oracle": "^0.2.6", + "@helium/data-credits-sdk": "0.2.14", + "@helium/distributor-oracle": "^0.2.14", "@helium/fanout-sdk": "0.1.2", - "@helium/helium-entity-manager-sdk": "^0.2.6", - "@helium/helium-react-hooks": "0.1.2", - "@helium/helium-sub-daos-sdk": "0.1.2", + "@helium/helium-entity-manager-sdk": "^0.2.14", + "@helium/helium-react-hooks": "0.2.14", + "@helium/helium-sub-daos-sdk": "0.2.14", "@helium/http": "4.7.5", "@helium/idls": "^0.2.5", "@helium/lazy-distributor-sdk": "0.1.2", diff --git a/src/App.tsx b/src/App.tsx index 6d0046446..785e415a2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,36 +1,37 @@ -import './polyfill' +import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' +import { PortalHost, PortalProvider } from '@gorhom/portal' +import { AccountProvider } from '@helium/helium-react-hooks' +import { DarkTheme, NavigationContainer } from '@react-navigation/native' +import MapboxGL from '@rnmapbox/maps' +import { ThemeProvider } from '@shopify/restyle' +import TokensProvider from '@storage/TokensProvider' +import globalStyles from '@theme/globalStyles' +import { darkThemeColors, lightThemeColors, theme } from '@theme/theme' +import { useColorScheme } from '@theme/themeHooks' +import * as SplashLib from 'expo-splash-screen' import React, { useMemo } from 'react' import { LogBox, Platform } from 'react-native' -import { ThemeProvider } from '@shopify/restyle' -import { DarkTheme, NavigationContainer } from '@react-navigation/native' import useAppState from 'react-native-appstate-hook' -import OneSignal, { OpenedEvent } from 'react-native-onesignal' import Config from 'react-native-config' -import { SafeAreaProvider } from 'react-native-safe-area-context' import { GestureHandlerRootView } from 'react-native-gesture-handler' -import { PortalHost, PortalProvider } from '@gorhom/portal' -import * as SplashLib from 'expo-splash-screen' -import { AccountProvider } from '@helium/helium-react-hooks' -import { theme, darkThemeColors, lightThemeColors } from '@theme/theme' -import { useColorScheme } from '@theme/themeHooks' -import globalStyles from '@theme/globalStyles' -import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' -import MapboxGL from '@rnmapbox/maps' -import useMount from './hooks/useMount' -import RootNavigator from './navigation/RootNavigator' -import { useAccountStorage } from './storage/AccountStorageProvider' -import LockScreen from './features/lock/LockScreen' -import SecurityScreen from './features/security/SecurityScreen' -import OnboardingProvider from './features/onboarding/OnboardingProvider' -import { BalanceProvider } from './utils/Balance' -import { useDeepLinking } from './utils/linking' -import { useNotificationStorage } from './storage/NotificationStorageProvider' +import OneSignal, { OpenedEvent } from 'react-native-onesignal' +import { SafeAreaProvider } from 'react-native-safe-area-context' import NetworkAwareStatusBar from './components/NetworkAwareStatusBar' +import SplashScreen from './components/SplashScreen' import WalletConnectProvider from './features/dappLogin/WalletConnectProvider' +import LockScreen from './features/lock/LockScreen' +import OnboardingProvider from './features/onboarding/OnboardingProvider' +import SecurityScreen from './features/security/SecurityScreen' +import useMount from './hooks/useMount' import { navigationRef } from './navigation/NavigationHelper' -import SplashScreen from './components/SplashScreen' +import RootNavigator from './navigation/RootNavigator' +import './polyfill' import { useSolana } from './solana/SolanaProvider' import WalletSignProvider from './solana/WalletSignProvider' +import { useAccountStorage } from './storage/AccountStorageProvider' +import { useNotificationStorage } from './storage/NotificationStorageProvider' +import { BalanceProvider } from './utils/Balance' +import { useDeepLinking } from './utils/linking' SplashLib.preventAutoHideAsync().catch(() => { /* reloading the app might trigger some race conditions, ignore them */ @@ -128,10 +129,12 @@ const App = () => { ref={navigationRef} > - - - - + + + + + + + balance?: BN index?: number payments?: Payment[] } @@ -60,12 +59,12 @@ export type HNTKeyboardRef = { } type Props = { - ticker: Ticker - networkFee?: Balance + mint?: PublicKey + networkFee?: BN children: ReactNode handleVisible?: (visible: boolean) => void onConfirmBalance: (opts: { - balance: Balance + balance: BN payee?: string index?: number }) => void @@ -77,7 +76,7 @@ const HNTKeyboardSelector = forwardRef( children, onConfirmBalance, handleVisible, - ticker, + mint, networkFee, usePortal = false, ...boxProps @@ -85,8 +84,10 @@ const HNTKeyboardSelector = forwardRef( ref: Ref, ) => { useImperativeHandle(ref, () => ({ show, hide })) + const decimals = useMint(mint)?.info?.decimals const { t } = useTranslation() const bottomSheetModalRef = useRef(null) + const { symbol } = useMetaplexMetadata(mint) const { backgroundStyle } = useOpacity('surfaceSecondary', 1) const [value, setValue] = useState('0') const [originalValue, setOriginalValue] = useState('') @@ -98,42 +99,10 @@ const HNTKeyboardSelector = forwardRef( const [headerHeight, setHeaderHeight] = useState(0) const containerStyle = useSafeTopPaddingStyle('android') const { handleDismiss, setIsShowing } = useBackHandler(bottomSheetModalRef) + const { currentAccount } = useAccountStorage() + const wallet = usePublicKey(currentAccount?.solanaAddress) - const { - floatToBalance, - hntBalance, - mobileBalance, - iotBalance, - dcBalance, - bonesToBalance, - solBalance, - } = useBalance() - - const getHeliumBalance = useMemo(() => { - switch (ticker) { - case 'HNT': - return hntBalance - case 'SOL': - return solBalance - case 'MOBILE': - return mobileBalance - case 'IOT': - return iotBalance - case 'DC': - return dcBalance - default: - return hntBalance - } - }, [dcBalance, iotBalance, mobileBalance, hntBalance, ticker, solBalance]) - - const isDntToken = useMemo(() => { - return ticker === 'IOT' || ticker === 'MOBILE' - }, [ticker]) - - const balanceForTicker = useMemo( - () => (ticker === 'HNT' ? hntBalance : getHeliumBalance), - [getHeliumBalance, hntBalance, ticker], - ) + const { amount: balanceForMint } = useOwnedAmount(wallet, mint) const snapPoints = useMemo(() => { const sheetHeight = containerHeight - headerHeight @@ -153,28 +122,20 @@ const HNTKeyboardSelector = forwardRef( const stripped = value .replaceAll(groupSeparator, '') .replaceAll(decimalSeparator, '.') - const numberVal = parseFloat(stripped) - if (ticker === 'DC') { - return new Balance(numberVal, CurrencyType.dataCredit) - } - - return floatToBalance(numberVal, ticker) - }, [floatToBalance, ticker, value]) + return new BN(stripped) + }, [value]) const hasMaxDecimals = useMemo(() => { - if (!valueAsBalance) return false + if (!valueAsBalance || typeof decimals === 'undefined') return false const valueString = value .replaceAll(groupSeparator, '') .replaceAll(decimalSeparator, '.') if (!valueString.includes('.')) return false - const [, decimals] = valueString.split('.') - return ( - decimals.length >= - (isDntToken ? 6 : valueAsBalance?.type.decimalPlaces.toNumber()) - ) - }, [value, valueAsBalance, isDntToken]) + const [, dec] = valueString.split('.') + return dec.length >= decimals + }, [value, valueAsBalance, decimals]) const getNextPayments = useCallback(() => { if (payments && paymentIndex !== undefined) { @@ -198,15 +159,16 @@ const HNTKeyboardSelector = forwardRef( setPayments(opts.payments) setContainerHeight(opts.containerHeight || 0) - const val = opts.balance?.floatBalance - .toLocaleString(locale, { maximumFractionDigits: 10 }) - .replaceAll(groupSeparator, '') + const val = + opts.balance && typeof decimals !== 'undefined' + ? humanReadable(opts.balance, decimals) + : undefined setValue(val || '0') bottomSheetModalRef.current?.present() setIsShowing(true) }, - [handleVisible, setIsShowing], + [handleVisible, setIsShowing, decimals], ) const hide = useCallback(() => { @@ -222,55 +184,38 @@ const HNTKeyboardSelector = forwardRef( const [maxEnabled, setMaxEnabled] = useState(false) const handleSetMax = useCallback(() => { - if (!solBalance || !getHeliumBalance || !networkFee) return + if (!valueAsBalance || !networkFee) return const currentAmount = getNextPayments() .filter((_v, index) => index !== paymentIndex || 0) // Remove the payment being updated - .reduce( - (prev, current) => { - if (!current.amount) { - return prev - } - return prev.plus(current.amount) - }, - ticker === 'DC' - ? new Balance(0, CurrencyType.dataCredit) - : bonesToBalance(0, ticker), - ) - - let maxBalance: Balance | undefined - if (ticker === 'SOL') { - maxBalance = solBalance.minus(currentAmount).minus(networkFee) - } else { - maxBalance = getHeliumBalance.minus(currentAmount) - } - - if (maxBalance.integerBalance < 0 && ticker !== 'DC') { - maxBalance = bonesToBalance(0, ticker) + .reduce((prev, current) => { + if (!current.amount) { + return prev + } + return prev.add(current.amount) + }, new BN(0)) + + let maxBalance: BN | undefined = balanceForMint + ? new BN(balanceForMint.toString()).sub(currentAmount) + : undefined + if (mint?.equals(NATIVE_MINT)) { + maxBalance = networkFee ? maxBalance?.sub(networkFee) : maxBalance } - const decimalPlaces = isDntToken - ? 6 - : maxBalance.type.decimalPlaces.toNumber() - - const val = floor(maxBalance.floatBalance, decimalPlaces) - .toLocaleString(locale, { - maximumFractionDigits: decimalPlaces, - }) - .replaceAll(groupSeparator, '') + const val = + maxBalance && decimals ? humanReadable(maxBalance, decimals) : '0' setValue(maxEnabled ? '0' : val) setMaxEnabled((m) => !m) }, [ - isDntToken, - getHeliumBalance, + valueAsBalance, networkFee, getNextPayments, - bonesToBalance, - ticker, + balanceForMint, + mint, + decimals, maxEnabled, paymentIndex, - solBalance, ]) const BackdropWrapper = useCallback( @@ -305,7 +250,7 @@ const HNTKeyboardSelector = forwardRef( {t('hntKeyboard.enterAmount', { - ticker: valueAsBalance?.type.ticker, + ticker: symbol, })} - {payer + {payer && balanceForMint && typeof decimals !== 'undefined' ? t('hntKeyboard.hntAvailable', { - amount: balanceToString(balanceForTicker, { - maxDecimalPlaces: 4, - }), + amount: + decimals && + humanReadable( + new BN(balanceForMint.toString()), + decimals, + ), }) : ''} @@ -347,13 +295,14 @@ const HNTKeyboardSelector = forwardRef( ), [ - balanceForTicker, + BackdropWrapper, handleHeaderLayout, - payeeAddress, - payer, t, - valueAsBalance, - BackdropWrapper, + symbol, + payer, + payeeAddress, + balanceForMint, + decimals, ], ) @@ -398,27 +347,17 @@ const HNTKeyboardSelector = forwardRef( const hasSufficientBalance = useMemo(() => { if (!payer) return true - if (!networkFee || !valueAsBalance || !hntBalance || !getHeliumBalance) { + if (!networkFee || !valueAsBalance || !balanceForMint) { return false } - if (ticker !== 'HNT') { - return getHeliumBalance.minus(valueAsBalance).integerBalance >= 0 - } - return hntBalance.minus(valueAsBalance).integerBalance >= 0 - }, [ - getHeliumBalance, - hntBalance, - networkFee, - payer, - ticker, - valueAsBalance, - ]) + new BN(balanceForMint.toString()).sub(valueAsBalance) + }, [networkFee, payer, valueAsBalance, balanceForMint]) const handleConfirm = useCallback(() => { bottomSheetModalRef.current?.dismiss() - if (!valueAsBalance) return + if (!valueAsBalance || typeof decimals === 'undefined') return onConfirmBalance({ balance: valueAsBalance, @@ -426,7 +365,7 @@ const HNTKeyboardSelector = forwardRef( index: paymentIndex, }) bottomSheetModalRef.current?.dismiss() - }, [payeeAddress, valueAsBalance, onConfirmBalance, paymentIndex]) + }, [valueAsBalance, decimals, onConfirmBalance, payeeAddress, paymentIndex]) const handleCancel = useCallback(() => { setValue(originalValue) @@ -523,7 +462,7 @@ const HNTKeyboardSelector = forwardRef( numberOfLines={1} adjustsFontSizeToFit > - {`${value || '0'} ${valueAsBalance?.type.ticker}`} + {`${value || '0'} ${symbol}`} {payer && networkFee && ( {t('hntKeyboard.fee', { - value: balanceToString(networkFee, { - maxDecimalPlaces: 4, - }), + value: networkFee && humanReadable(networkFee, 9), })} )} diff --git a/src/components/TokenButton.tsx b/src/components/TokenButton.tsx index c92112d53..a7cec4148 100644 --- a/src/components/TokenButton.tsx +++ b/src/components/TokenButton.tsx @@ -1,39 +1,22 @@ -import React, { memo, useCallback, useMemo } from 'react' import ChevronDown from '@assets/images/chevronDown.svg' -import { Keyboard, StyleSheet } from 'react-native' +import useHaptic from '@hooks/useHaptic' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { BoxProps } from '@shopify/restyle' -import TokenSOL from '@assets/images/tokenSOL.svg' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import { Ticker } from '@helium/currency' -import { useColors, useHitSlop } from '@theme/themeHooks' +import { PublicKey } from '@solana/web3.js' import { Color, Theme } from '@theme/theme' -import useHaptic from '@hooks/useHaptic' +import { useColors, useHitSlop } from '@theme/themeHooks' +import React, { memo, useCallback, useMemo } from 'react' +import { Keyboard, StyleSheet } from 'react-native' import Box from './Box' import Text from './Text' +import TokenIcon from './TokenIcon' import TouchableOpacityBox from './TouchableOpacityBox' -const TokenItem = ({ ticker }: { ticker: Ticker }) => { - const colors = useColors() - const color = useMemo(() => { - return ticker === 'MOBILE' ? 'blueBright500' : 'white' - }, [ticker]) - +const TokenItem = ({ mint }: { mint?: PublicKey }) => { + const { json } = useMetaplexMetadata(mint) return ( - {ticker === 'SOL' && ( - - )} - {ticker === 'HNT' && ( - - )} - - {ticker === 'MOBILE' && ( - - )} - - {ticker === 'IOT' && } + ) } @@ -45,7 +28,7 @@ type Props = { subtitle?: string showBubbleArrow?: boolean innerBoxProps?: BoxProps - ticker: Ticker + mint?: PublicKey } & BoxProps const TokenButton = ({ @@ -55,7 +38,7 @@ const TokenButton = ({ subtitle, showBubbleArrow, innerBoxProps, - ticker, + mint, backgroundColor: backgroundColorProps, ...boxProps }: Props) => { @@ -90,7 +73,7 @@ const TokenButton = ({ paddingVertical={innerBoxProps?.paddingVertical || 'm'} {...innerBoxProps} > - + {title} diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx index b473e0f8b..50b5abfaa 100644 --- a/src/components/TokenIcon.tsx +++ b/src/components/TokenIcon.tsx @@ -1,24 +1,38 @@ -import React from 'react' -import TokenHNT from '@assets/images/tokenHNT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' import TokenDC from '@assets/images/tokenDC.svg' -import TokenSOL from '@assets/images/tokenSolana.svg' +import TokenHNT from '@assets/images/tokenHNT.svg' import TokenIOT from '@assets/images/tokenIOT.svg' +import TokenMOBILE from '@assets/images/tokenMOBILE.svg' import TokenSolWhite from '@assets/images/tokenSOL.svg' +import TokenSOL from '@assets/images/tokenSolana.svg' import { Ticker } from '@helium/currency' import { useColors } from '@theme/themeHooks' -import Box from './Box' +import React from 'react' +import { Image } from 'react-native' import BackgroundFill from './BackgroundFill' +import Box from './Box' type Props = { - ticker: Ticker + ticker?: Ticker size?: number white?: boolean + img?: string } -const TokenIcon = ({ ticker, size = 40, white }: Props) => { +const TokenIcon = ({ ticker, size = 40, white, img }: Props) => { const colors = useColors() + if (img) { + return ( + + ) + } + switch (ticker) { default: case 'HNT': diff --git a/src/components/TokenSelector.tsx b/src/components/TokenSelector.tsx index 2ba7ba8be..12002448a 100644 --- a/src/components/TokenSelector.tsx +++ b/src/components/TokenSelector.tsx @@ -1,33 +1,56 @@ +import TokenIcon from '@components/TokenIcon' +import { + BottomSheetBackdrop, + BottomSheetFlatList, + BottomSheetModal, + BottomSheetModalProvider, +} from '@gorhom/bottom-sheet' +import { Ticker } from '@helium/currency' +import useBackHandler from '@hooks/useBackHandler' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { BoxProps } from '@shopify/restyle' +import { PublicKey } from '@solana/web3.js' +import { Theme } from '@theme/theme' +import { useColors, useOpacity } from '@theme/themeHooks' import React, { - forwardRef, - memo, ReactNode, Ref, + forwardRef, + memo, useCallback, useImperativeHandle, useMemo, useRef, } from 'react' -import { - BottomSheetBackdrop, - BottomSheetFlatList, - BottomSheetModal, - BottomSheetModalProvider, -} from '@gorhom/bottom-sheet' -import { BoxProps } from '@shopify/restyle' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { Ticker } from '@helium/currency' -import { useColors, useOpacity } from '@theme/themeHooks' -import { Theme } from '@theme/theme' -import useBackHandler from '@hooks/useBackHandler' import Box from './Box' import ListItem, { LIST_ITEM_HEIGHT } from './ListItem' export type TokenListItem = { - label: string - icon: ReactNode - value: Ticker + mint: PublicKey + selected: boolean +} + +const ProvidedListItem = ({ + mint, + onPress, + selected, +}: { + mint: PublicKey + onPress: () => void selected: boolean +}) => { + const { symbol, json } = useMetaplexMetadata(mint) + return ( + : undefined} + onPress={onPress} + selected={selected} + paddingStart="l" + hasDivider + /> + ) } export type TokenSelectorRef = { @@ -35,7 +58,7 @@ export type TokenSelectorRef = { } type Props = { children: ReactNode - onTokenSelected: (type: Ticker) => void + onTokenSelected: (mint: PublicKey) => void tokenData: TokenListItem[] } & BoxProps const TokenSelector = forwardRef( @@ -76,19 +99,16 @@ const TokenSelector = forwardRef( ) const keyExtractor = useCallback((item: TokenListItem) => { - return item.value + return item.mint }, []) const renderFlatlistItem = useCallback( ({ item }: { item: TokenListItem; index: number }) => { return ( - handleTokenPress(item.mint)} + mint={item.mint} /> ) }, diff --git a/src/features/account/AccountTokenList.tsx b/src/features/account/AccountTokenList.tsx index 3265b3f73..9bf72a49f 100644 --- a/src/features/account/AccountTokenList.tsx +++ b/src/features/account/AccountTokenList.tsx @@ -1,51 +1,64 @@ -import React, { useCallback, useMemo } from 'react' -import Balance, { AnyCurrencyType, Ticker } from '@helium/currency' -import { times, without } from 'lodash' -import { useSafeAreaInsets } from 'react-native-safe-area-context' +import Config from '@assets/images/config.svg' +import Text from '@components/Text' +import TouchableOpacityBox from '@components/TouchableOpacityBox' import { BottomSheetFlatList } from '@gorhom/bottom-sheet' import { BottomSheetFlatListProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetScrollable/types' +import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { useNavigation } from '@react-navigation/native' +import { PublicKey } from '@solana/web3.js' +import { useVisibleTokens } from '@storage/TokensProvider' import { useBalance } from '@utils/Balance' +import { times } from 'lodash' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { HomeNavigationProp } from '../home/homeTypes' import TokenListItem, { TokenSkeleton } from './TokenListItem' -type Token = { - type: Ticker - balance: Balance - staked: boolean +type Props = { + onLayout?: BottomSheetFlatListProps['onLayout'] } -type Props = { - onLayout?: BottomSheetFlatListProps['onLayout'] +const sortValues: Record = { + [HNT_MINT.toBase58()]: 10, + [IOT_MINT.toBase58()]: 9, + [MOBILE_MINT.toBase58()]: 8, + [DC_MINT.toBase58()]: 7, +} +export function getSortValue(mint: string): number { + return sortValues[mint] || 0 } const AccountTokenList = ({ onLayout }: Props) => { - const { solBalance, hntBalance, mobileBalance, dcBalance, iotBalance } = - useBalance() + const navigation = useNavigation() + const { t } = useTranslation() + const { visibleTokens } = useVisibleTokens() + + const onManageTokenList = useCallback(() => { + navigation.navigate('AccountManageTokenListScreen') + }, [navigation]) + const { tokenAccounts } = useBalance() const { bottom } = useSafeAreaInsets() + const mints = useMemo(() => { + return tokenAccounts + ?.filter( + (ta) => + visibleTokens.has(ta.mint) && + ta.balance > 0 && + (ta.decimals > 0 || ta.mint === DC_MINT.toBase58()), + ) + .map((ta) => new PublicKey(ta.mint)) + .sort((a, b) => { + return getSortValue(b.toBase58()) - getSortValue(a.toBase58()) + }) + }, [tokenAccounts, visibleTokens]) const bottomSpace = useMemo(() => bottom * 2, [bottom]) - const tokens = useMemo(() => { - const allTokens = [ - hntBalance, - mobileBalance, - iotBalance, - dcBalance, - solBalance, - ] - return without(allTokens, undefined) as Balance[] - }, [dcBalance, hntBalance, iotBalance, mobileBalance, solBalance]) - - const renderItem = useCallback( - ({ - item: token, - }: { - // eslint-disable-next-line react/no-unused-prop-types - item: Balance - }) => { - return - }, - [], - ) + // eslint-disable-next-line react/no-unused-prop-types + const renderItem = useCallback(({ item }: { item: PublicKey }) => { + return + }, []) const renderEmptyComponent = useCallback(() => { return ( @@ -57,8 +70,24 @@ const AccountTokenList = ({ onLayout }: Props) => { ) }, []) - const keyExtractor = useCallback((item: Balance) => { - return item.type.ticker + const renderFooterComponent = useCallback(() => { + return ( + + + + {t('accountTokenList.manage')} + + + ) + }, [onManageTokenList, t]) + + const keyExtractor = useCallback((mint: PublicKey) => { + return mint.toBase58() }, []) const contentContainerStyle = useMemo( @@ -70,7 +99,8 @@ const AccountTokenList = ({ onLayout }: Props) => { return ( { contentContainerStyle={contentContainerStyle} renderItem={renderItem} ListEmptyComponent={renderEmptyComponent} + ListFooterComponent={renderFooterComponent} keyExtractor={keyExtractor} - onLayout={onLayout} /> ) } diff --git a/src/features/account/AccountTokenScreen.tsx b/src/features/account/AccountTokenScreen.tsx index 76cc2885d..20623c24c 100644 --- a/src/features/account/AccountTokenScreen.tsx +++ b/src/features/account/AccountTokenScreen.tsx @@ -1,45 +1,47 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react' -import { RouteProp, useRoute } from '@react-navigation/native' -import { useTranslation } from 'react-i18next' +import ActivityIndicator from '@components/ActivityIndicator' +import { ReAnimatedBox } from '@components/AnimatedBox' +import BackScreen from '@components/BackScreen' +import BlurActionSheet from '@components/BlurActionSheet' +import Box from '@components/Box' +import FadeInOut, { DelayedFadeIn } from '@components/FadeInOut' +import ListItem from '@components/ListItem' +import { NavBarHeight } from '@components/NavBar' +import Text from '@components/Text' +import TokenIcon from '@components/TokenIcon' +import TouchableOpacityBox from '@components/TouchableOpacityBox' import BottomSheet, { BottomSheetFlatList, WINDOW_HEIGHT, } from '@gorhom/bottom-sheet' +import { Ticker } from '@helium/currency' +import useLayoutHeight from '@hooks/useLayoutHeight' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import { RouteProp, useRoute } from '@react-navigation/native' +import globalStyles from '@theme/globalStyles' +import { useColors } from '@theme/themeHooks' +import React, { useCallback, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Platform, View } from 'react-native' import Animated, { FadeIn, useAnimatedStyle, useSharedValue, } from 'react-native-reanimated' -import { Platform, View } from 'react-native' -import { Ticker } from '@helium/currency' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import BackScreen from '@components/BackScreen' -import Box from '@components/Box' -import Text from '@components/Text' -import ListItem from '@components/ListItem' -import TokenIcon from '@components/TokenIcon' -import BlurActionSheet from '@components/BlurActionSheet' -import useLayoutHeight from '@hooks/useLayoutHeight' -import FadeInOut, { DelayedFadeIn } from '@components/FadeInOut' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import ActivityIndicator from '@components/ActivityIndicator' -import { ReAnimatedBox } from '@components/AnimatedBox' -import globalStyles from '@theme/globalStyles' -import { useColors } from '@theme/themeHooks' -import { NavBarHeight } from '@components/NavBar' +import { useSolana } from '../../solana/SolanaProvider' import { useAccountStorage } from '../../storage/AccountStorageProvider' +import { Activity } from '../../types/activity' +import { HomeStackParamList } from '../home/homeTypes' import AccountActionBar from './AccountActionBar' import { FilterType, useActivityFilter } from './AccountActivityFilter' -import TxnListItem from './TxnListItem' +import AccountTokenBalance from './AccountTokenBalance' +import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance' import { useTransactionDetail, withTransactionDetail, } from './TransactionDetail' -import { HomeStackParamList } from '../home/homeTypes' -import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance' -import AccountTokenBalance from './AccountTokenBalance' -import { Activity } from '../../types/activity' -import { useSolana } from '../../solana/SolanaProvider' +import TxnListItem from './TxnListItem' import useSolanaActivityList from './useSolanaActivityList' const delayedAnimation = FadeIn.delay(300) @@ -69,10 +71,12 @@ const AccountTokenScreen = () => { setOnEndReachedCalledDuringMomentum, ] = useState(true) - const routeTicker = useMemo( - () => route.params.tokenType?.toUpperCase() as Ticker, - [route.params.tokenType], - ) + const mintStr = useMemo(() => route.params.mint, [route.params.mint]) + const mint = usePublicKey(mintStr) + + const { symbol } = useMetaplexMetadata(mint) + + const routeTicker = useMemo(() => symbol?.toUpperCase() as Ticker, [symbol]) const toggleFiltersOpen = useCallback( (open) => () => { @@ -97,7 +101,8 @@ const AccountTokenScreen = () => { } = useSolanaActivityList({ account: currentAccount, filter: filterState.filter, - ticker: routeTicker, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + mint: mint!, }) const handleOnFetchMoreActivity = useCallback(() => { diff --git a/src/features/account/TokenListItem.tsx b/src/features/account/TokenListItem.tsx index a944bda97..25dc5a6c9 100644 --- a/src/features/account/TokenListItem.tsx +++ b/src/features/account/TokenListItem.tsx @@ -1,40 +1,45 @@ -import Balance, { AnyCurrencyType } from '@helium/currency' -import React, { useCallback, useMemo } from 'react' import Arrow from '@assets/images/listItemRight.svg' -import { useNavigation } from '@react-navigation/native' import Box from '@components/Box' import FadeInOut from '@components/FadeInOut' import Text from '@components/Text' -import TouchableContainer from '@components/TouchableContainer' import TokenIcon from '@components/TokenIcon' +import TouchableContainer from '@components/TouchableContainer' +import { Ticker } from '@helium/currency' +import { useOwnedAmount } from '@helium/helium-react-hooks' +import { humanReadable } from '@helium/spl-utils' import useHaptic from '@hooks/useHaptic' -import { balanceToString } from '@utils/Balance' -import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import { useNavigation } from '@react-navigation/native' +import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import BN from 'bn.js' +import React, { useCallback, useMemo } from 'react' import { HomeNavigationProp } from '../home/homeTypes' +import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance' export const ITEM_HEIGHT = 72 type Props = { - balance: Balance + mint: PublicKey } -const TokenListItem = ({ balance }: Props) => { +const TokenListItem = ({ mint }: Props) => { const navigation = useNavigation() + const { currentAccount } = useAccountStorage() + const wallet = usePublicKey(currentAccount?.solanaAddress) + const { amount, decimals } = useOwnedAmount(wallet, mint) const { triggerImpact } = useHaptic() + const { json, symbol } = useMetaplexMetadata(mint) const handleNavigation = useCallback(() => { triggerImpact('light') navigation.navigate('AccountTokenScreen', { - tokenType: balance.type.ticker, + mint: mint.toBase58(), }) - }, [navigation, balance, triggerImpact]) + }, [navigation, mint, triggerImpact]) const balanceToDisplay = useMemo(() => { - return ( - balanceToString(balance, { - maxDecimalPlaces: 9, - showTicker: false, - }) || 0 - ) - }, [balance]) + return amount ? humanReadable(new BN(amount.toString()), decimals) : '' + }, [amount, decimals]) return ( @@ -48,7 +53,7 @@ const TokenListItem = ({ balance }: Props) => { borderBottomColor="primaryBackground" borderBottomWidth={1} > - + { color="secondaryText" maxFontSizeMultiplier={1.3} > - {balance.type.ticker} + {symbol} - + {symbol && ( + + )} @@ -78,6 +85,8 @@ const TokenListItem = ({ balance }: Props) => { ) } +export default TokenListItem + export const TokenSkeleton = () => { return ( @@ -104,5 +113,3 @@ export const TokenSkeleton = () => { ) } - -export default TokenListItem diff --git a/src/features/account/useSolanaActivityList.tsx b/src/features/account/useSolanaActivityList.tsx index 7c00eb5f5..87d17fcb4 100644 --- a/src/features/account/useSolanaActivityList.tsx +++ b/src/features/account/useSolanaActivityList.tsx @@ -1,24 +1,25 @@ -import { Ticker } from '@helium/currency' +import { DC_MINT } from '@helium/spl-utils' +import { PublicKey } from '@solana/web3.js' +import { onLogs, removeAccountChangeListener } from '@utils/solanaUtils' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useSelector } from 'react-redux' -import { Mints } from '@utils/constants' -import { onLogs, removeAccountChangeListener } from '@utils/solanaUtils' +import { useSolana } from '../../solana/SolanaProvider' import { CSAccount } from '../../storage/cloudStorage' import { RootState } from '../../store/rootReducer' import { getTxns } from '../../store/slices/solanaSlice' import { useAppDispatch } from '../../store/store' import { FilterType } from './AccountActivityFilter' -import { useSolana } from '../../solana/SolanaProvider' export default ({ account, filter, - ticker, + mint, }: { account?: CSAccount | null filter: FilterType - ticker: Ticker + mint: PublicKey }) => { + const mintStr = mint.toBase58() const [now, setNow] = useState(new Date()) const dispatch = useAppDispatch() const { anchorProvider } = useSolana() @@ -40,8 +41,7 @@ export default ({ dispatch( getTxns({ account, - ticker, - mints: Mints, + mint: mintStr, requestType: 'start_fresh', anchorProvider, }), @@ -54,8 +54,7 @@ export default ({ dispatch( getTxns({ account, - ticker, - mints: Mints, + mint: mintStr, requestType: 'update_head', anchorProvider, }), @@ -67,7 +66,7 @@ export default ({ removeAccountChangeListener(anchorProvider, accountSubscriptionId.current) } accountSubscriptionId.current = subId - }, [account, dispatch, filter, ticker, anchorProvider]) + }, [account, dispatch, filter, mintStr, anchorProvider]) const requestMore = useCallback(() => { if (!account?.address || !anchorProvider) return @@ -75,27 +74,29 @@ export default ({ dispatch( getTxns({ account, - mints: Mints, - ticker, + mint: mintStr, requestType: 'fetch_more', anchorProvider, }), ) - }, [account, dispatch, ticker, anchorProvider]) + }, [account, dispatch, anchorProvider, mintStr]) const data = useMemo(() => { if (!account?.solanaAddress || !solanaActivity.data[account.solanaAddress]) return [] - if (ticker === 'DC' && (filter === 'delegate' || filter === 'mint')) { - return solanaActivity.data[account.solanaAddress][filter][ticker] + if ( + mintStr === DC_MINT.toBase58() && + (filter === 'delegate' || filter === 'mint') + ) { + return solanaActivity.data[account.solanaAddress][filter][mintStr] } if (filter !== 'in' && filter !== 'out' && filter !== 'all') return [] if (filter === 'in' || filter === 'out') { const payments = solanaActivity.data[account.solanaAddress]?.payment[ - ticker - ]?.filter((txn) => txn.tokenType === ticker) + mintStr + ]?.filter((txn) => txn.mint === mintStr) return payments?.filter((txn) => filter === 'out' ? txn.payee === account.solanaAddress @@ -103,10 +104,10 @@ export default ({ ) } - return solanaActivity.data[account.solanaAddress][filter][ticker]?.filter( - (txn) => txn.tokenType === ticker, + return solanaActivity.data[account.solanaAddress][filter][mintStr]?.filter( + (txn) => txn.mint === mintStr, ) - }, [account, filter, solanaActivity.data, ticker]) + }, [account, filter, solanaActivity.data, mintStr]) const loading = useMemo(() => { return solanaActivity.loading diff --git a/src/features/burn/BurnScreen.tsx b/src/features/burn/BurnScreen.tsx index 71e31060b..544f59aaf 100644 --- a/src/features/burn/BurnScreen.tsx +++ b/src/features/burn/BurnScreen.tsx @@ -1,66 +1,64 @@ -import React, { - useCallback, - memo as reactMemo, - useMemo, - useEffect, - useState, - useRef, -} from 'react' -import { useTranslation } from 'react-i18next' import Close from '@assets/images/close.svg' import QR from '@assets/images/qr.svg' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import { Platform } from 'react-native' -import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' -import { Edge, useSafeAreaInsets } from 'react-native-safe-area-context' -import Balance, { CurrencyType, Ticker } from '@helium/currency' -import Address, { NetTypes } from '@helium/address' -import { TokenBurnV1 } from '@helium/transactions' -import { useSelector } from 'react-redux' -import Box from '@components/Box' -import Text from '@components/Text' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { useColors, useHitSlop } from '@theme/themeHooks' +import AccountButton from '@components/AccountButton' import AccountSelector, { AccountSelectorRef, } from '@components/AccountSelector' -import AccountButton from '@components/AccountButton' -import SubmitButton from '@components/SubmitButton' +import Box from '@components/Box' import LedgerBurnModal, { LedgerBurnModalRef, } from '@components/LedgerBurnModal' -import useAlert from '@hooks/useAlert' +import SafeAreaBox from '@components/SafeAreaBox' +import SubmitButton from '@components/SubmitButton' +import Text from '@components/Text' +import TokenButton from '@components/TokenButton' import TokenSelector, { TokenListItem, TokenSelectorRef, } from '@components/TokenSelector' -import TokenButton from '@components/TokenButton' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' -import { Mints } from '@utils/constants' +import TouchableOpacityBox from '@components/TouchableOpacityBox' +import Address, { NetTypes } from '@helium/address' +import Balance, { CurrencyType, mint } from '@helium/currency' +import { IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { TokenBurnV1 } from '@helium/transactions' +import useAlert from '@hooks/useAlert' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' -import SafeAreaBox from '@components/SafeAreaBox' -import { TXN_FEE_IN_SOL } from '../../utils/solanaUtils' +import { useColors, useHitSlop } from '@theme/themeHooks' +import React, { + memo as reactMemo, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { Platform } from 'react-native' +import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' +import { Edge, useSafeAreaInsets } from 'react-native-safe-area-context' +import { useSelector } from 'react-redux' +import AddressBookSelector, { + AddressBookRef, +} from '../../components/AddressBookSelector' +import HNTKeyboard, { HNTKeyboardRef } from '../../components/HNTKeyboard' +import IconPressedContainer from '../../components/IconPressedContainer' +import useSubmitTxn from '../../hooks/useSubmitTxn' +import { useAccountStorage } from '../../storage/AccountStorageProvider' +import { CSAccount } from '../../storage/cloudStorage' import { RootState } from '../../store/rootReducer' -import { HomeNavigationProp, HomeStackParamList } from '../home/homeTypes' +import { balanceToString, useBalance } from '../../utils/Balance' import { accountNetType, ellipsizeAddress, formatAccountAlias, solAddressIsValid, } from '../../utils/accountUtils' -import { useAccountStorage } from '../../storage/AccountStorageProvider' -import { balanceToString, useBalance } from '../../utils/Balance' -import PaymentSummary from '../payment/PaymentSummary' -import PaymentSubmit from '../payment/PaymentSubmit' -import HNTKeyboard, { HNTKeyboardRef } from '../../components/HNTKeyboard' -import IconPressedContainer from '../../components/IconPressedContainer' +import { TXN_FEE_IN_SOL } from '../../utils/solanaUtils' +import { HomeNavigationProp, HomeStackParamList } from '../home/homeTypes' import PaymentItem from '../payment/PaymentItem' -import AddressBookSelector, { - AddressBookRef, -} from '../../components/AddressBookSelector' -import { CSAccount } from '../../storage/cloudStorage' -import useSubmitTxn from '../../hooks/useSubmitTxn' +import PaymentSubmit from '../payment/PaymentSubmit' +import PaymentSummary from '../payment/PaymentSummary' type Route = RouteProp const BurnScreen = () => { @@ -75,7 +73,7 @@ const BurnScreen = () => { const { top } = useSafeAreaInsets() const navigation = useNavigation() const { t } = useTranslation() - const { primaryText, blueBright500 } = useColors() + const { primaryText } = useColors() const ledgerPaymentRef = useRef(null) const hitSlop = useHitSlop('l') const accountSelectorRef = useRef(null) @@ -99,11 +97,9 @@ const BurnScreen = () => { const delegatePayment = useSelector( (reduxState: RootState) => reduxState.solana.delegate, ) - const [ticker, setTicker] = useState('MOBILE') + const [mint, setMint] = useState(MOBILE_MINT) const tokenSelectorRef = useRef(null) - const mint = useMemo(() => new PublicKey(Mints[ticker]), [ticker]) - const { isDelegate } = useMemo(() => route.params, [route.params]) const containerStyle = useMemo( @@ -241,7 +237,7 @@ const BurnScreen = () => { const errStrings: string[] = [] if (insufficientFunds) { errStrings.push( - t('payment.insufficientFunds', { token: amountBalance?.type.ticker }), + t('payment.insufficientFunds', { token: amountBalance?.type.mint }), ) } @@ -299,8 +295,8 @@ const BurnScreen = () => { [networkType, isDelegate], ) - const onTickerSelected = useCallback((tick: Ticker) => { - setTicker(tick) + const onMintSelected = useCallback((tick: mint) => { + setMint(tick) }, []) const handleTokenTypeSelected = useCallback(() => { @@ -310,19 +306,15 @@ const BurnScreen = () => { const data = useMemo( (): TokenListItem[] => [ { - label: 'MOBILE', - icon: , - value: 'MOBILE' as Ticker, - selected: ticker === 'MOBILE', + mint: MOBILE_MINT, + selected: mint.equals(MOBILE_MINT), }, { - label: 'IOT', - icon: , - value: 'IOT' as Ticker, - selected: ticker === 'IOT', + mint: IOT_MINT, + selected: mint.equals(IOT_MINT), }, ], - [blueBright500, ticker], + [mint], ) if (!amountBalance) return null @@ -331,13 +323,13 @@ const BurnScreen = () => { @@ -417,13 +409,13 @@ const BurnScreen = () => { {isDelegate ? ( @@ -438,7 +430,7 @@ const BurnScreen = () => { }) }} handleAddressError={handleAddressError} - ticker={amountBalance?.type.ticker} + mint={mint} address={delegateAddress} amount={amountBalance} hasError={hasError} diff --git a/src/features/home/HomeNavigator.tsx b/src/features/home/HomeNavigator.tsx index aa40b6ce8..c2f096971 100644 --- a/src/features/home/HomeNavigator.tsx +++ b/src/features/home/HomeNavigator.tsx @@ -1,25 +1,26 @@ -import React, { memo } from 'react' +import ConfirmPinScreen from '@components/ConfirmPinScreen' import { createStackNavigator, StackNavigationOptions, } from '@react-navigation/stack' -import ConfirmPinScreen from '@components/ConfirmPinScreen' -import AccountAssignScreen from '../onboarding/AccountAssignScreen' +import React, { memo } from 'react' import AccountsScreen from '../account/AccountsScreen' -import PaymentScreen from '../payment/PaymentScreen' -import AddressBookNavigator from '../addressBook/AddressBookNavigator' -import SettingsNavigator from '../settings/SettingsNavigator' +import AccountTokenScreen from '../account/AccountTokenScreen' +import AirdropScreen from '../account/AirdropScreen' +import AccountManageTokenListScreen from '../account/AccountManageTokenListScreen' import AddNewContact from '../addressBook/AddNewContact' -import NotificationsNavigator from '../notifications/NotificationsNavigator' -import RequestScreen from '../request/RequestScreen' -import PaymentQrScanner from '../payment/PaymentQrScanner' +import AddressBookNavigator from '../addressBook/AddressBookNavigator' import AddressQrScanner from '../addressBook/AddressQrScanner' -import AccountTokenScreen from '../account/AccountTokenScreen' -import AddNewAccountNavigator from './addNewAccount/AddNewAccountNavigator' -import ImportAccountNavigator from '../onboarding/import/ImportAccountNavigator' import BurnScreen from '../burn/BurnScreen' +import NotificationsNavigator from '../notifications/NotificationsNavigator' +import AccountAssignScreen from '../onboarding/AccountAssignScreen' +import ImportAccountNavigator from '../onboarding/import/ImportAccountNavigator' +import PaymentQrScanner from '../payment/PaymentQrScanner' +import PaymentScreen from '../payment/PaymentScreen' +import RequestScreen from '../request/RequestScreen' +import SettingsNavigator from '../settings/SettingsNavigator' import SwapNavigator from '../swaps/SwapNavigator' -import AirdropScreen from '../account/AirdropScreen' +import AddNewAccountNavigator from './addNewAccount/AddNewAccountNavigator' const HomeStack = createStackNavigator() @@ -39,6 +40,10 @@ const HomeStackScreen = () => { name="AccountTokenScreen" component={AccountTokenScreen} /> + + amount?: BN hasError?: boolean max?: boolean - createTokenAccountFee?: Balance< - NetworkTokens | TestNetworkTokens | IotTokens | MobileTokens - > + createTokenAccountFee?: BN } & BoxProps type Props = { @@ -57,7 +52,7 @@ type Props = { onRemove?: (index: number) => void onUpdateError?: (index: number, hasError: boolean) => void hideMemo?: boolean - ticker?: string + mint?: PublicKey netType?: number showAmount?: boolean } & Payment @@ -68,6 +63,7 @@ const PaymentItem = ({ account, address, amount, + decimals, fee, handleAddressError, hasError, @@ -80,7 +76,7 @@ const PaymentItem = ({ onRemove, onToggleMax, onUpdateError, - ticker, + mint, showAmount = true, ...boxProps }: Props) => { @@ -88,6 +84,7 @@ const PaymentItem = ({ const { dcToNetworkTokens, oraclePrice } = useBalance() const { t } = useTranslation() const { secondaryText } = useColors() + const { symbol } = useMetaplexMetadata(mint) const addressIsWrongNetType = useMemo( () => @@ -235,7 +232,7 @@ const PaymentItem = ({ {showAmount && ( - {!amount || amount?.integerBalance === 0 ? ( + {!amount || amount?.isZero() ? ( <> {t('payment.enterAmount', { - ticker, + symbol, })} @@ -266,9 +263,7 @@ const PaymentItem = ({ variant="subtitle2" color="primaryText" > - {balanceToString(amount, { - maxDecimalPlaces: amount.type.decimalPlaces.toNumber(), - })} + {humanReadable(amount, decimals)} {fee && ( diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 1a557e556..54e39d90b 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -1,79 +1,78 @@ -import React, { - useCallback, - useState, - memo as reactMemo, - useMemo, - useEffect, - useRef, -} from 'react' -import { useTranslation } from 'react-i18next' import Close from '@assets/images/close.svg' import QR from '@assets/images/qr.svg' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import Balance, { - CurrencyType, - NetworkTokens, - TestNetworkTokens, - Ticker, -} from '@helium/currency' -import { Keyboard, Platform } from 'react-native' -import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' -import Address, { NetTypes } from '@helium/address' -import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { PaymentV2 } from '@helium/transactions' -import { unionBy } from 'lodash' -import Toast from 'react-native-simple-toast' -import { useSelector } from 'react-redux' -import TokenButton from '@components/TokenButton' -import Box from '@components/Box' -import Text from '@components/Text' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { useColors, useHitSlop } from '@theme/themeHooks' +import TokenHNT from '@assets/images/tokenHNT.svg' +import TokenIOT from '@assets/images/tokenIOT.svg' +import TokenMOBILE from '@assets/images/tokenMOBILE.svg' +import TokenSOL from '@assets/images/tokenSOL.svg' +import AccountButton from '@components/AccountButton' import AccountSelector, { AccountSelectorRef, } from '@components/AccountSelector' -import TokenSelector, { - TokenListItem, - TokenSelectorRef, -} from '@components/TokenSelector' -import AccountButton from '@components/AccountButton' import AddressBookSelector, { AddressBookRef, } from '@components/AddressBookSelector' +import Box from '@components/Box' import HNTKeyboard, { HNTKeyboardRef } from '@components/HNTKeyboard' -import useDisappear from '@hooks/useDisappear' import IconPressedContainer from '@components/IconPressedContainer' -import TokenSOL from '@assets/images/tokenSOL.svg' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' -import { calcCreateAssociatedTokenAccountAccountFee } from '@utils/solanaUtils' -import { Mints } from '@utils/constants' +import Text from '@components/Text' +import TokenButton from '@components/TokenButton' +import TokenSelector, { + TokenListItem, + TokenSelectorRef, +} from '@components/TokenSelector' +import TouchableOpacityBox from '@components/TouchableOpacityBox' +import Address, { NetTypes } from '@helium/address' +import { CurrencyType, Ticker } from '@helium/currency' +import { useOwnedAmount } from '@helium/helium-react-hooks' +import { HNT_MINT } from '@helium/spl-utils' +import useDisappear from '@hooks/useDisappear' +import { usePublicKey } from '@hooks/usePublicKey' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' +import { useColors, useHitSlop } from '@theme/themeHooks' +import { Mints } from '@utils/constants' +import { calcCreateAssociatedTokenAccountAccountFee } from '@utils/solanaUtils' +import BN from 'bn.js' +import { unionBy } from 'lodash' +import React, { + memo as reactMemo, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { Keyboard, Platform } from 'react-native' +import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import Toast from 'react-native-simple-toast' +import { useSelector } from 'react-redux' +import useSubmitTxn from '../../hooks/useSubmitTxn' +import { RootNavigationProp } from '../../navigation/rootTypes' import { useSolana } from '../../solana/SolanaProvider' -import { - HomeNavigationProp, - HomeStackParamList, - PaymentRouteParam, -} from '../home/homeTypes' +import { useAccountStorage } from '../../storage/AccountStorageProvider' +import { CSAccount } from '../../storage/cloudStorage' +import { RootState } from '../../store/rootReducer' +import { solanaSlice } from '../../store/slices/solanaSlice' +import { useAppDispatch } from '../../store/store' +import { balanceToString, useBalance } from '../../utils/Balance' import { accountNetType, formatAccountAlias, solAddressIsValid, } from '../../utils/accountUtils' -import { useAccountStorage } from '../../storage/AccountStorageProvider' -import { balanceToString, useBalance } from '../../utils/Balance' -import PaymentItem from './PaymentItem' -import usePaymentsReducer, { MAX_PAYMENTS } from './usePaymentsReducer' +import { SendDetails } from '../../utils/linking' +import { + HomeNavigationProp, + HomeStackParamList, + PaymentRouteParam, +} from '../home/homeTypes' +import * as logger from '../../utils/logger' import PaymentCard from './PaymentCard' +import PaymentItem from './PaymentItem' import PaymentSubmit from './PaymentSubmit' -import { CSAccount } from '../../storage/cloudStorage' -import { RootState } from '../../store/rootReducer' -import { useAppDispatch } from '../../store/store' -import { solanaSlice } from '../../store/slices/solanaSlice' -import { RootNavigationProp } from '../../navigation/rootTypes' -import useSubmitTxn from '../../hooks/useSubmitTxn' -import { SendDetails } from '../../utils/linking' +import usePaymentsReducer, { MAX_PAYMENTS } from './usePaymentsReducer' type LinkedPayment = { amount?: string @@ -104,16 +103,8 @@ const PaymentScreen = () => { const accountSelectorRef = useRef(null) const tokenSelectorRef = useRef(null) const hntKeyboardRef = useRef(null) - const { oraclePrice, hntBalance, solBalance, iotBalance, mobileBalance } = - useBalance() - const { anchorProvider } = useSolana() - - const appDispatch = useAppDispatch() - const navigation = useNavigation() - const rootNav = useNavigation() - const { t } = useTranslation() - const { primaryText, blueBright500, white } = useColors() - const hitSlop = useHitSlop('l') + const { oraclePrice } = useBalance() + const [mint, setMint] = useState(HNT_MINT) const { currentAccount, currentNetworkAddress, @@ -122,14 +113,21 @@ const PaymentScreen = () => { setCurrentAccount, sortedAccountsForNetType, } = useAccountStorage() - const [ticker, setTicker] = useState( - (route.params?.defaultTokenType?.toUpperCase() as Ticker) || 'HNT', - ) - const [mint, setMint] = useState( - (route.params?.defaultTokenType?.toUpperCase() as Ticker) - ? Mints[route.params?.defaultTokenType?.toUpperCase() as Ticker] - : Mints.HNT, - ) + const wallet = usePublicKey(currentAccount?.solanaAddress) + const { amount: balanceBigint } = useOwnedAmount(wallet, mint) + const balance = useMemo(() => { + if (typeof balanceBigint !== 'undefined') { + return new BN(balanceBigint.toString()) + } + }, [balanceBigint]) + const { anchorProvider } = useSolana() + + const appDispatch = useAppDispatch() + const navigation = useNavigation() + const rootNav = useNavigation() + const { t } = useTranslation() + const { primaryText, blueBright500, white } = useColors() + const hitSlop = useHitSlop('l') useDisappear(() => { appDispatch(solanaSlice.actions.resetPayment()) @@ -161,18 +159,14 @@ const PaymentScreen = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [route]) - const currencyType = useMemo(() => CurrencyType.fromTicker(ticker), [ticker]) - const [paymentState, dispatch] = usePaymentsReducer({ - currencyType, + mint, oraclePrice, - accountMobileBalance: mobileBalance, - accountIotBalance: iotBalance, - accountNetworkBalance: hntBalance, + balance, netType: networkType, }) - const { submitPayment, submitLedger } = useSubmitTxn() + const { submitPayment } = useSubmitTxn() const solanaPayment = useSelector( (reduxState: RootState) => reduxState.solana.payment, @@ -241,11 +235,7 @@ const PaymentScreen = () => { }, [route]) const handleBalance = useCallback( - (opts: { - balance: Balance - payee?: string - index?: number - }) => { + (opts: { balance: BN; payee?: string; index?: number }) => { if (opts.index === undefined || !currentAccount) return dispatch({ @@ -274,7 +264,7 @@ const PaymentScreen = () => { paymentState.payments.length < MAX_PAYMENTS && !!lastPayee.address && !!lastPayee.amount && - lastPayee.amount.integerBalance > 0 + lastPayee.amount?.gt(new BN(0)) ) }, [currentAccount, paymentState.payments]) @@ -293,21 +283,23 @@ const PaymentScreen = () => { [paymentState.payments], ) - const handleSubmit = useCallback( - async (opts?: { txn: PaymentV2; txnJson: string }) => { - try { - if (!opts) { - await submitPayment(payments) - } else { - // This is a ledger device - submitLedger() - } - } catch (e) { - console.error(e) - } - }, - [payments, submitPayment, submitLedger], - ) + const handleSubmit = useCallback(async () => { + try { + await submitPayment( + paymentState.payments + .filter((p) => p.address && p.amount) + .map((payment) => ({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + payee: payment.address!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + balanceAmount: payment.amount!, + })), + paymentState.mint, + ) + } catch (e) { + logger.error(e) + } + }, [submitPayment, paymentState.mint, paymentState.payments]) const insufficientFunds = useMemo((): [ value: boolean, @@ -325,22 +317,22 @@ const PaymentScreen = () => { solBalance.minus(paymentState.networkFee).integerBalance >= 0 } let hasEnoughToken = false - if (ticker === 'MOBILE' && mobileBalance) { + if (mint === 'MOBILE' && mobileBalance) { hasEnoughToken = mobileBalance.minus(paymentState.totalAmount).integerBalance >= 0 - } else if (ticker === 'IOT' && iotBalance) { + } else if (mint === 'IOT' && iotBalance) { hasEnoughToken = iotBalance.minus(paymentState.totalAmount).integerBalance >= 0 - } else if (ticker === 'HNT' && hntBalance) { + } else if (mint === 'HNT' && hntBalance) { hasEnoughToken = hntBalance.minus(paymentState.totalAmount).integerBalance >= 0 - } else if (ticker === 'SOL' && solBalance) { + } else if (mint === 'SOL' && solBalance) { hasEnoughToken = solBalance.minus(paymentState.totalAmount).integerBalance >= 0 } if (!hasEnoughSol) return [true, 'SOL' as Ticker] - if (!hasEnoughToken) return [true, paymentState.totalAmount.type.ticker] + if (!hasEnoughToken) return [true, paymentState.totalAmount.type.mint] return [false, ''] } catch (e) { // if the screen was already open, then a deep link of a different net type @@ -355,7 +347,7 @@ const PaymentScreen = () => { paymentState.totalAmount, paymentState.networkFee, solBalance, - ticker, + mint, mobileBalance, iotBalance, ]) @@ -432,12 +424,12 @@ const PaymentScreen = () => { const onTickerSelected = useCallback( (tick: Ticker) => { - setTicker(tick) + setMint(tick) setMint(Mints[tick]) dispatch({ type: 'changeToken', - currencyType: CurrencyType.fromTicker(tick), + mint: CurrencyType.fromTicker(tick), }) }, [dispatch], @@ -616,7 +608,7 @@ const PaymentScreen = () => { }, [sortedAccountsForNetType]) const tokenButtonBalance = useMemo(() => { - switch (ticker) { + switch (mint) { case 'HNT': return balanceToString(hntBalance) case 'SOL': @@ -626,7 +618,7 @@ const PaymentScreen = () => { case 'IOT': return balanceToString(iotBalance) } - }, [ticker, hntBalance, solBalance, mobileBalance, iotBalance]) + }, [mint, hntBalance, solBalance, mobileBalance, iotBalance]) const data = useMemo((): TokenListItem[] => { const tokens = [ @@ -634,37 +626,37 @@ const PaymentScreen = () => { label: 'HNT', icon: , value: 'HNT' as Ticker, - selected: ticker === 'HNT', + selected: mint === 'HNT', }, { label: 'MOBILE', icon: , value: 'MOBILE' as Ticker, - selected: ticker === 'MOBILE', + selected: mint === 'MOBILE', }, { label: 'IOT', icon: , value: 'IOT' as Ticker, - selected: ticker === 'IOT', + selected: mint === 'IOT', }, { label: 'SOL', icon: , value: 'SOL' as Ticker, - selected: ticker === 'SOL', + selected: mint === 'SOL', }, ] return tokens - }, [blueBright500, white, ticker]) + }, [blueBright500, white, mint]) return ( <> @@ -743,13 +735,13 @@ const PaymentScreen = () => { {paymentState.payments.map((p, index) => ( @@ -770,7 +762,7 @@ const PaymentScreen = () => { onEditAddress={handleEditAddress} handleAddressError={handleAddressError} onUpdateError={handleSetPaymentError} - ticker={currencyType.ticker} + mint={mint.mint} onRemove={ paymentState.payments.length > 1 ? handleRemove @@ -801,7 +793,7 @@ const PaymentScreen = () => { + createTokenAccountFee: BN } type UpdateBalanceAction = { type: 'updateBalance' address?: string index: number - value?: Balance + value?: BN payer: string } @@ -58,7 +46,7 @@ type AddPayee = { type ChangeToken = { type: 'changeToken' - currencyType: PaymentCurrencyType + mint: PublicKey } type AddLinkedPayments = { @@ -74,40 +62,37 @@ export const MAX_PAYMENTS = 10 type PaymentState = { payments: Payment[] - totalAmount: Balance + totalAmount: BN error?: string - currencyType: PaymentCurrencyType + mint: PublicKey oraclePrice?: Balance netType: NetTypes.NetType - networkFee?: Balance - accountMobileBalance?: Balance - accountIotBalance?: Balance - accountNetworkBalance?: Balance + networkFee?: BN + balance: BN } const initialState = (opts: { - currencyType: PaymentCurrencyType + mint: PublicKey payments?: Payment[] netType: NetTypes.NetType oraclePrice?: Balance - accountMobileBalance?: Balance - accountIotBalance?: Balance - accountNetworkBalance?: Balance + balance?: BN }): PaymentState => ({ error: undefined, payments: [{}] as Array, - totalAmount: new Balance(0, opts.currencyType), + totalAmount: new BN(0), + balance: opts.balance || new BN(0), ...calculateFee([{}]), ...opts, }) -const paymentsSum = (payments: Payment[], type: PaymentCurrencyType) => { +const paymentsSum = (payments: Payment[]) => { return payments.reduce((prev, current) => { if (!current.amount) { return prev } - return prev.plus(current.amount) - }, new Balance(0, type)) + return prev.add(current.amount) + }, new BN(0)) } const calculateFee = (payments: Payment[]) => { @@ -115,14 +100,11 @@ const calculateFee = (payments: Payment[]) => { if (!current.createTokenAccountFee) { return prev } - return prev.plus(current.createTokenAccountFee) - }, new Balance(0, CurrencyType.solTokens)) + return prev.add(current.createTokenAccountFee) + }, new BN(0)) - const txnFeeInLammportsFee = new Balance( - TXN_FEE_IN_LAMPORTS, - CurrencyType.solTokens, - ) - const networkFee = totalFee.plus(txnFeeInLammportsFee) + const txnFeeInLammportsFee = new BN(TXN_FEE_IN_LAMPORTS) + const networkFee = totalFee.add(txnFeeInLammportsFee) return { networkFee, @@ -130,22 +112,21 @@ const calculateFee = (payments: Payment[]) => { } const recalculate = (payments: Payment[], state: PaymentState) => { - const accountBalance = getAccountBalance(state) + const accountBalance = state.balance const { networkFee } = calculateFee(payments) const maxPayment = payments.find((p) => p.max) - const totalAmount = paymentsSum(payments, state.currencyType) + const totalAmount = paymentsSum(payments) if (!maxPayment) { return { networkFee, payments, totalAmount } } - const prevPaymentAmount = - maxPayment?.amount ?? new Balance(0, state.currencyType) + const prevPaymentAmount = maxPayment?.amount ?? new BN(0) - const totalMinusPrevPayment = totalAmount.minus(prevPaymentAmount) - let maxBalance = accountBalance?.minus(totalMinusPrevPayment) + const totalMinusPrevPayment = totalAmount.sub(prevPaymentAmount) + let maxBalance = accountBalance?.sub(totalMinusPrevPayment) - if ((maxBalance?.integerBalance ?? 0) < 0) { - maxBalance = new Balance(0, state.currencyType) + if (maxBalance.lt(new BN(0))) { + maxBalance = new BN(0) } maxPayment.amount = maxBalance @@ -153,25 +134,8 @@ const recalculate = (payments: Payment[], state: PaymentState) => { return { networkFee, payments, - totalAmount: paymentsSum(payments, state.currencyType), - } -} - -const getAccountBalance = ({ - accountIotBalance, - accountMobileBalance, - accountNetworkBalance, - currencyType, -}: PaymentState) => { - if (currencyType.ticker === CurrencyType.iot.ticker) { - return accountIotBalance + totalAmount: paymentsSum(payments), } - - if (currencyType.ticker === CurrencyType.mobile.ticker) { - return accountMobileBalance - } - - return accountNetworkBalance } function reducer( @@ -266,12 +230,10 @@ function reducer( }) return initialState({ - currencyType: action.currencyType, + mint: action.mint, payments: newPayments, oraclePrice: state.oraclePrice, - accountMobileBalance: state.accountMobileBalance, - accountIotBalance: state.accountIotBalance, - accountNetworkBalance: state.accountNetworkBalance, + balance: state.balance, netType: state.netType, }) } @@ -279,11 +241,9 @@ function reducer( case 'addLinkedPayments': { if (!action.payments.length) { return initialState({ - currencyType: state.currencyType, + mint: state.mint, oraclePrice: state.oraclePrice, - accountMobileBalance: state.accountMobileBalance, - accountIotBalance: state.accountIotBalance, - accountNetworkBalance: state.accountNetworkBalance, + balance: state.balance, netType: state.netType, }) } @@ -291,12 +251,10 @@ function reducer( const nextPayments: Payment[] = action.payments.map((p) => ({ address: p.address, account: p.account, - amount: p.amount - ? new Balance(parseInt(p.amount, 10), state.currencyType) - : undefined, - createTokenAccountFee: new Balance(0, CurrencyType.solTokens), + amount: p.amount ? new BN(parseInt(p.amount, 10)) : undefined, + createTokenAccountFee: new BN(0), })) - const totalAmount = paymentsSum(nextPayments, state.currencyType) + const totalAmount = paymentsSum(nextPayments) const fees = calculateFee(nextPayments) @@ -337,9 +295,7 @@ function reducer( export default (opts: { netType: NetTypes.NetType - currencyType: PaymentCurrencyType + mint: PublicKey oraclePrice?: Balance - accountMobileBalance?: Balance - accountIotBalance?: Balance - accountNetworkBalance?: Balance + balance?: BN }) => useReducer(reducer, initialState(opts)) diff --git a/src/features/request/RequestScreen.tsx b/src/features/request/RequestScreen.tsx index 2b2e368b2..c86147064 100644 --- a/src/features/request/RequestScreen.tsx +++ b/src/features/request/RequestScreen.tsx @@ -1,71 +1,67 @@ +import ShareIcon from '@assets/images/share.svg' +import AccountButton from '@components/AccountButton' +import AccountSelector, { + AccountSelectorRef, +} from '@components/AccountSelector' +import BackgroundFill from '@components/BackgroundFill' +import Box from '@components/Box' +import FadeInOut from '@components/FadeInOut' +import HNTKeyboard, { HNTKeyboardRef } from '@components/HNTKeyboard' +import TabBar, { TabBarOption } from '@components/TabBar' +import Text from '@components/Text' +import TokenButton from '@components/TokenButton' +import TokenSelector, { + TokenListItem, + TokenSelectorRef, +} from '@components/TokenSelector' +import TouchableOpacityBox from '@components/TouchableOpacityBox' +import { NetTypes as NetType } from '@helium/address' +import { useMint } from '@helium/helium-react-hooks' +import { humanReadable } from '@helium/spl-utils' +import useHaptic from '@hooks/useHaptic' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import Clipboard from '@react-native-community/clipboard' +import { useKeyboard } from '@react-native-community/hooks' +import { useNavigation } from '@react-navigation/native' +import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { useVisibleTokens } from '@storage/TokensProvider' +import { + useBorderRadii, + useColors, + useOpacity, + useSpacing, +} from '@theme/themeHooks' +import animateTransition from '@utils/animateTransition' +import { makePayRequestLink } from '@utils/linking' +import BN from 'bn.js' import React, { memo, useCallback, - useMemo, - useState, useEffect, + useMemo, useRef, + useState, } from 'react' import { useTranslation } from 'react-i18next' -import { useNavigation } from '@react-navigation/native' import { + ActivityIndicator, Keyboard, LayoutChangeEvent, - ActivityIndicator, Platform, } from 'react-native' -import Share, { ShareOptions } from 'react-native-share' -import Clipboard from '@react-native-community/clipboard' -import Toast from 'react-native-simple-toast' import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' -import ShareIcon from '@assets/images/share.svg' -import { useDebounce } from 'use-debounce' -import { useKeyboard } from '@react-native-community/hooks' -import Balance, { - CurrencyType, - MobileTokens, - NetworkTokens, - TestNetworkTokens, - Ticker, -} from '@helium/currency' -import { NetTypes as NetType } from '@helium/address' import QRCode from 'react-native-qrcode-svg' import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated' -import TabBar, { TabBarOption } from '@components/TabBar' -import Text from '@components/Text' -import { useAccountStorage } from '@storage/AccountStorageProvider' -import Box from '@components/Box' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { - useBorderRadii, - useColors, - useOpacity, - useSpacing, -} from '@theme/themeHooks' -import { balanceToString } from '@utils/Balance' -import AccountButton from '@components/AccountButton' -import AccountSelector, { - AccountSelectorRef, -} from '@components/AccountSelector' -import { makePayRequestLink } from '@utils/linking' -import useHaptic from '@hooks/useHaptic' -import BackgroundFill from '@components/BackgroundFill' -import animateTransition from '@utils/animateTransition' -import HNTKeyboard, { HNTKeyboardRef } from '@components/HNTKeyboard' -import TokenButton from '@components/TokenButton' -import TokenSelector, { - TokenListItem, - TokenSelectorRef, -} from '@components/TokenSelector' -import FadeInOut from '@components/FadeInOut' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' +import Share, { ShareOptions } from 'react-native-share' +import Toast from 'react-native-simple-toast' +import { useDebounce } from 'use-debounce' const QR_CONTAINER_SIZE = 220 type RequestType = 'qr' | 'link' const RequestScreen = () => { + const { visibleTokens } = useVisibleTokens() const { currentAccount, currentNetworkAddress: networkAddress } = useAccountStorage() const { t } = useTranslation() @@ -77,25 +73,22 @@ const RequestScreen = () => { const navigation = useNavigation() const { l } = useSpacing() const { l: borderRadius } = useBorderRadii() - const { secondaryText, primaryText, white, blueBright500 } = useColors() + const { secondaryText, primaryText } = useColors() const [isEditing, setIsEditing] = useState(false) const { keyboardShown } = useKeyboard() const hntKeyboardRef = useRef(null) const [hntKeyboardVisible, setHNTKeyboardVisible] = useState(false) - const [paymentAmount, setPaymentAmount] = - useState>() - const [ticker, setTicker] = useState('HNT') + const [paymentAmount, setPaymentAmount] = useState() + const [mint, setMint] = useState() const tokenSelectorRef = useRef(null) const qrRef = useRef<{ toDataURL: (callback: (url: string) => void) => void }>(null) + const decimals = useMint(mint)?.info?.decimals + const { symbol } = useMetaplexMetadata(mint) const handleBalance = useCallback( - (opts: { - balance: Balance - payee?: string - index?: number - }) => { + (opts: { balance: BN; payee?: string; index?: number }) => { setPaymentAmount(opts.balance) }, [setPaymentAmount], @@ -127,9 +120,9 @@ const RequestScreen = () => { return makePayRequestLink({ payee: networkAddress, balanceAmount: paymentAmount, - defaultTokenType: ticker, + mint: mint?.toBase58(), }) - }, [networkAddress, paymentAmount, ticker]) + }, [networkAddress, paymentAmount, mint]) const [qrLink] = useDebounce(link, 500) @@ -204,8 +197,6 @@ const RequestScreen = () => { triggerNavHaptic() }, [link, showToast, triggerNavHaptic]) - const currencyType = useMemo(() => CurrencyType.fromTicker(ticker), [ticker]) - const requestTypeOptions = useMemo( (): Array => [ { title: t('request.qr'), value: 'qr' }, @@ -218,43 +209,23 @@ const RequestScreen = () => { tokenSelectorRef?.current?.showTokens() }, []) - const onTickerSelected = useCallback((tick: Ticker) => { - setTicker(tick) - }, []) - const handleAccountButtonPress = useCallback(() => { if (!accountSelectorRef?.current) return accountSelectorRef?.current?.show() }, []) const data = useMemo((): TokenListItem[] => { - const tokens = [ - { - label: 'HNT', - icon: , - value: 'HNT' as Ticker, - selected: ticker === 'HNT', - }, - { - label: 'MOBILE', - icon: , - value: 'MOBILE' as Ticker, - selected: ticker === 'MOBILE', - }, - { - label: 'IOT', - icon: , - value: 'IOT' as Ticker, - selected: ticker === 'IOT', - }, - ] - - return tokens - }, [blueBright500, white, ticker]) + return [...visibleTokens].map((m) => { + return { + selected: mint?.toBase58() === m, + mint: new PublicKey(m), + } + }) + }, [visibleTokens, mint]) return ( { { /> { {t('request.amount')} - {!paymentAmount || paymentAmount.integerBalance === 0 ? ( + {!paymentAmount || paymentAmount.isZero() ? ( {t('request.enterAmount', { - ticker: paymentAmount?.type.ticker, + ticker: symbol, })} ) : ( - {balanceToString(paymentAmount)} + {typeof decimals !== 'undefined' && + humanReadable(paymentAmount, decimals)} )} diff --git a/src/features/swaps/SwapItem.tsx b/src/features/swaps/SwapItem.tsx index cf570072e..5415e1b94 100644 --- a/src/features/swaps/SwapItem.tsx +++ b/src/features/swaps/SwapItem.tsx @@ -1,19 +1,20 @@ -import React, { memo, useCallback, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import { Ticker } from '@helium/currency' -import { GestureResponderEvent, Pressable, StyleSheet } from 'react-native' -import { BoxProps } from '@shopify/restyle' -import { useCreateOpacity } from '@theme/themeHooks' -import Text from '@components/Text' import Box from '@components/Box' +import Text from '@components/Text' import TokenIcon from '@components/TokenIcon' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { BoxProps } from '@shopify/restyle' +import { PublicKey } from '@solana/web3.js' import { Theme } from '@theme/theme' +import { useCreateOpacity } from '@theme/themeHooks' +import React, { memo, useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { GestureResponderEvent, Pressable, StyleSheet } from 'react-native' import CarotDown from '../../assets/images/carotDownFull.svg' export type SwapItemProps = { isPaying: boolean onCurrencySelect: () => void - currencySelected: Ticker + mintSelected: PublicKey amount: number loading?: boolean onPress?: ((event: GestureResponderEvent) => void) | null | undefined @@ -23,7 +24,7 @@ export type SwapItemProps = { const SwapItem = ({ isPaying, onCurrencySelect, - currencySelected, + mintSelected: currencySelected, amount, loading = false, onPress, @@ -31,6 +32,7 @@ const SwapItem = ({ ...rest }: SwapItemProps) => { const { t } = useTranslation() + const { symbol, json } = useMetaplexMetadata(currencySelected) const { backgroundStyle: generateBackgroundStyle } = useCreateOpacity() @@ -80,7 +82,7 @@ const SwapItem = ({ alignItems="center" borderRadius="round" > - + - {currencySelected} + {symbol} @@ -98,7 +100,7 @@ const SwapItem = ({ ) - }, [currencySelected, getBackgroundColorStylePill, onCurrencySelect]) + }, [symbol, json, getBackgroundColorStylePill, onCurrencySelect]) return ( diff --git a/src/features/swaps/SwapScreen.tsx b/src/features/swaps/SwapScreen.tsx index 7bc5ad35a..5a4224102 100644 --- a/src/features/swaps/SwapScreen.tsx +++ b/src/features/swaps/SwapScreen.tsx @@ -1,16 +1,13 @@ import Menu from '@assets/images/menu.svg' -import Refresh from '@assets/images/refresh.svg' -import TokenDC from '@assets/images/tokenDC.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' import Plus from '@assets/images/plus.svg' +import Refresh from '@assets/images/refresh.svg' import AddressBookSelector, { AddressBookRef, } from '@components/AddressBookSelector' import { ReAnimatedBox } from '@components/AnimatedBox' import Box from '@components/Box' import ButtonPressable from '@components/ButtonPressable' +import CircleLoader from '@components/CircleLoader' import CloseButton from '@components/CloseButton' import HNTKeyboard, { HNTKeyboardRef } from '@components/HNTKeyboard' import SafeAreaBox from '@components/SafeAreaBox' @@ -20,27 +17,36 @@ import TextTransform from '@components/TextTransform' import TokenSelector, { TokenSelectorRef } from '@components/TokenSelector' import TouchableOpacityBox from '@components/TouchableOpacityBox' import TreasuryWarningScreen from '@components/TreasuryWarningScreen' -import Balance, { CurrencyType, SolTokens, Ticker } from '@helium/currency' +import Balance, { CurrencyType } from '@helium/currency' +import { useMint } from '@helium/helium-react-hooks' +import { + DC_MINT, + HNT_MINT, + IOT_MINT, + MOBILE_MINT, + toNumber, +} from '@helium/spl-utils' import { useTreasuryPrice } from '@hooks/useTreasuryPrice' import { useNavigation } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' import { useAccountStorage } from '@storage/AccountStorageProvider' import { CSAccount } from '@storage/cloudStorage' -import { Mints } from '@utils/constants' -import { getAtaAccountCreationFee, TXN_FEE_IN_SOL } from '@utils/solanaUtils' +import { useColors, useHitSlop } from '@theme/themeHooks' +import { TXN_FEE_IN_SOL, getAtaAccountCreationFee } from '@utils/solanaUtils' +import BN from 'bn.js' import React, { memo, useCallback, useMemo, useRef, useState } from 'react' import { useAsync } from 'react-async-hook' import { useTranslation } from 'react-i18next' import { LayoutAnimation } from 'react-native' import { Edge } from 'react-native-safe-area-context' -import { useColors, useHitSlop } from '@theme/themeHooks' -import CircleLoader from '@components/CircleLoader' import useSubmitTxn from '../../hooks/useSubmitTxn' -import { solAddressIsValid } from '../../utils/accountUtils' +import { useSolana } from '../../solana/SolanaProvider' import { useBalance } from '../../utils/Balance' +import { solAddressIsValid } from '../../utils/accountUtils' import SwapItem from './SwapItem' import { SwapNavigationProp } from './swapTypes' -import { useSolana } from '../../solana/SolanaProvider' + +const SOL_TXN_FEE = new BN(TXN_FEE_IN_SOL) // Selector Mode enum enum SelectorMode { @@ -48,14 +54,6 @@ enum SelectorMode { youReceive = 'youReceive', } -enum Tokens { - HNT = 'HNT', - MOBILE = 'MOBILE', - IOT = 'IOT', - SOL = 'SOL', - DC = 'DC', -} - const SwapScreen = () => { const { t } = useTranslation() const { currentAccount } = useAccountStorage() @@ -64,12 +62,10 @@ const SwapScreen = () => { const { submitTreasurySwap, submitMintDataCredits } = useSubmitTxn() const edges = useMemo(() => ['bottom'] as Edge[], []) const [selectorMode, setSelectorMode] = useState(SelectorMode.youPay) - const [youPayTokenType, setYouPayTokenType] = useState(Tokens.MOBILE) + const [youPayMint, setYouPayMint] = useState(MOBILE_MINT) const colors = useColors() const [youPayTokenAmount, setYouPayTokenAmount] = useState(0) - const [youReceiveTokenType, setYouReceiveTokenType] = useState( - Tokens.HNT, - ) + const [youReceiveMint, setYouReceiveMint] = useState(HNT_MINT) const [solFee, setSolFee] = useState(undefined) const [hasInsufficientBalance, setHasInsufficientBalance] = useState< undefined | boolean @@ -82,7 +78,7 @@ const SwapScreen = () => { price, loading: loadingPrice, freezeDate, - } = useTreasuryPrice(new PublicKey(Mints[youPayTokenType]), youPayTokenAmount) + } = useTreasuryPrice(youPayMint, youPayTokenAmount) const [swapping, setSwapping] = useState(false) const [transactionError, setTransactionError] = useState() const [hasRecipientError, setHasRecipientError] = useState(false) @@ -114,18 +110,18 @@ const SwapScreen = () => { // If user does not have enough tokens to swap for greater than 0.00000001 tokens const insufficientTokensToSwap = useMemo(() => { if ( - youPayTokenType === Tokens.HNT && + youPayMint.equals(HNT_MINT) && (hntBalance?.floatBalance || 0) < 0.00000001 ) { return true } return ( - youPayTokenType !== Tokens.HNT && + !youPayMint.equals(HNT_MINT) && !(price && price > 0) && youPayTokenAmount > 0 ) - }, [hntBalance, price, youPayTokenAmount, youPayTokenType]) + }, [hntBalance, price, youPayTokenAmount, youPayMint]) const showError = useMemo(() => { if (hasRecipientError) return t('generic.notValidSolanaAddress') @@ -150,8 +146,8 @@ const SwapScreen = () => { const refresh = useCallback(async () => { setYouPayTokenAmount(0) - setYouReceiveTokenType(Tokens.HNT) - setYouPayTokenType(Tokens.MOBILE) + setYouReceiveMint(HNT_MINT) + setYouPayMint(MOBILE_MINT) setSelectorMode(SelectorMode.youPay) setSolFee(undefined) setNetworkError(undefined) @@ -166,15 +162,14 @@ const SwapScreen = () => { ) return - const toMint = new PublicKey(Mints[youReceiveTokenType]) let fee = TXN_FEE_IN_SOL const ataFee = await getAtaAccountCreationFee({ solanaAddress: currentAccount.solanaAddress, connection, - mint: toMint, + mint: youReceiveMint, }) - fee += ataFee.floatBalance + fee += ataFee.toNumber() setSolFee(fee) @@ -185,8 +180,8 @@ const SwapScreen = () => { anchorProvider, currentAccount?.solanaAddress, solBalance, - youReceiveTokenType, - youPayTokenType, + youReceiveMint, + youPayMint, ]) const handleClose = useCallback(() => { @@ -217,80 +212,56 @@ const SwapScreen = () => { }, [refresh, t, handleClose]) const setTokenTypeHandler = useCallback( - (ticker: Ticker) => { + (mint: PublicKey) => { if (selectorMode === SelectorMode.youPay) { refresh() - setYouPayTokenType(ticker) + setYouPayMint(mint) } if (selectorMode === SelectorMode.youReceive) { - setYouReceiveTokenType(ticker) + setYouReceiveMint(mint) } if ( selectorMode === SelectorMode.youPay && - ticker !== Tokens.HNT && - youReceiveTokenType === Tokens.DC + !mint.equals(HNT_MINT) && + !youReceiveMint.equals(DC_MINT) ) { - setYouReceiveTokenType(Tokens.HNT) + setYouReceiveMint(HNT_MINT) setYouPayTokenAmount(0) } - if (selectorMode === SelectorMode.youPay && ticker === Tokens.HNT) { - setYouReceiveTokenType(Tokens.DC) + if (selectorMode === SelectorMode.youPay && mint.equals(HNT_MINT)) { + setYouReceiveMint(DC_MINT) } - if (selectorMode === SelectorMode.youReceive && ticker === Tokens.HNT) { - setYouPayTokenType(Tokens.MOBILE) + if (selectorMode === SelectorMode.youReceive && mint.equals(HNT_MINT)) { + setYouPayMint(MOBILE_MINT) } - if (selectorMode === SelectorMode.youReceive && ticker === Tokens.DC) { - setYouPayTokenType(Tokens.HNT) + if (selectorMode === SelectorMode.youReceive && mint.equals(DC_MINT)) { + setYouPayMint(HNT_MINT) } }, - [refresh, selectorMode, youReceiveTokenType], + [refresh, selectorMode, youReceiveMint], ) const tokenData = useMemo(() => { const tokens = { - [SelectorMode.youPay]: [ - { - label: Tokens.MOBILE, - icon: , - value: Tokens.MOBILE, - selected: youPayTokenType === Tokens.MOBILE, - }, - { - label: Tokens.HNT, - icon: , - value: Tokens.HNT, - selected: youPayTokenType === Tokens.HNT, - }, - { - label: Tokens.IOT, - icon: , - value: Tokens.IOT, - selected: youPayTokenType === Tokens.IOT, - }, - ], - [SelectorMode.youReceive]: [ - { - label: Tokens.HNT, - icon: , - value: Tokens.HNT, - selected: youReceiveTokenType === Tokens.HNT, - }, - { - label: Tokens.DC, - icon: , - value: Tokens.DC, - selected: youReceiveTokenType === Tokens.DC, - }, - ], + [SelectorMode.youPay]: [MOBILE_MINT, HNT_MINT, IOT_MINT].map((mint) => ({ + mint, + selected: youPayMint.equals(mint), + })), + [SelectorMode.youReceive]: [MOBILE_MINT, HNT_MINT, IOT_MINT].map( + (mint) => ({ + mint, + selected: youReceiveMint.equals(mint), + }), + ), } return tokens[selectorMode] - }, [selectorMode, youPayTokenType, youReceiveTokenType]) + }, [selectorMode, youPayMint, youReceiveMint]) const onCurrencySelect = useCallback( (youPay: boolean) => () => { @@ -300,27 +271,33 @@ const SwapScreen = () => { [], ) + const decimals = useMint(youPayMint)?.info?.decimals + const onTokenItemPressed = useCallback(() => { - hntKeyboardRef.current?.show({ - payer: currentAccount, - }) - }, [currentAccount]) + if (typeof decimals !== undefined) { + hntKeyboardRef.current?.show({ + payer: currentAccount, + }) + } + }, [currentAccount, decimals]) const onConfirmBalance = useCallback( - ({ balance }: { balance: Balance }) => { - const amount = balance.floatBalance.valueOf() + ({ balance }: { balance: BN }) => { + if (typeof decimals === 'undefined') return + + const amount = toNumber(balance, decimals) setYouPayTokenAmount(amount) }, - [], + [decimals], ) const hitSlop = useHitSlop('l') const youReceiveTokenAmount = useMemo(() => { - if (price && youPayTokenType !== Tokens.HNT) { + if (price && !youPayMint.equals(HNT_MINT)) { return price } - if (youPayTokenType === Tokens.HNT && currentAccount) { + if (youPayMint.equals(HNT_MINT) && currentAccount) { const networkTokens = Balance.fromFloat( Number(youPayTokenAmount), CurrencyType.networkToken, @@ -332,13 +309,7 @@ const SwapScreen = () => { } return 0 - }, [ - currentAccount, - networkTokensToDc, - price, - youPayTokenAmount, - youPayTokenType, - ]) + }, [currentAccount, networkTokensToDc, price, youPayTokenAmount, youPayMint]) const handleSwapTokens = useCallback(async () => { if (connection) { @@ -358,26 +329,22 @@ const SwapScreen = () => { ? new PublicKey(recipient) : new PublicKey(currentAccount.solanaAddress) - if (youPayTokenType === Tokens.HNT) { + if (youPayMint.equals(HNT_MINT)) { await submitMintDataCredits({ dcAmount: youReceiveTokenAmount, recipient: recipientAddr, }) } - if (youPayTokenType !== Tokens.HNT) { - await submitTreasurySwap( - new PublicKey(Mints[youPayTokenType]), - youPayTokenAmount, - recipientAddr, - ) + if (!youPayMint.equals(HNT_MINT)) { + await submitTreasurySwap(youPayMint, youPayTokenAmount, recipientAddr) } setSwapping(false) navigation.push('SwappingScreen', { - tokenA: youPayTokenType, - tokenB: youReceiveTokenType, + tokenA: youPayMint.toBase58(), + tokenB: youReceiveMint.toBase58(), }) } catch (error) { setSwapping(false) @@ -388,9 +355,9 @@ const SwapScreen = () => { connection, currentAccount, recipient, - youPayTokenType, + youPayMint, navigation, - youReceiveTokenType, + youReceiveMint, submitMintDataCredits, youReceiveTokenAmount, submitTreasurySwap, @@ -408,11 +375,8 @@ const SwapScreen = () => { { marginHorizontal="m" isPaying onCurrencySelect={onCurrencySelect(true)} - currencySelected={youPayTokenType} + mintSelected={youPayMint} amount={youPayTokenAmount} /> @@ -439,7 +403,7 @@ const SwapScreen = () => { marginHorizontal="m" isPaying={false} onCurrencySelect={onCurrencySelect(false)} - currencySelected={youReceiveTokenType} + mintSelected={youReceiveMint} amount={youReceiveTokenAmount} loading={loadingPrice} /> diff --git a/src/features/swaps/SwappingScreen.tsx b/src/features/swaps/SwappingScreen.tsx index ede5117b8..a42614f88 100644 --- a/src/features/swaps/SwappingScreen.tsx +++ b/src/features/swaps/SwappingScreen.tsx @@ -31,6 +31,8 @@ const SwappingScreen = () => { const { t } = useTranslation() const { tokenA, tokenB } = route.params + const { jsonA } = useMetaplexMetadata(usePublicKey(tokenA)) + const { jsonB } = useMetaplexMetadata(usePublicKey(tokenB)) const solanaPayment = useSelector( (reduxState: RootState) => reduxState.solana.payment, @@ -53,7 +55,7 @@ const SwappingScreen = () => { padding="s" marginEnd="m" > - + { borderRadius="round" padding="s" > - + ) - }, [tokenA, tokenB]) + }, [jsonA?.img, jsonB?.img]) return ( diff --git a/src/hooks/useSubmitTxn.ts b/src/hooks/useSubmitTxn.ts index 2450691bc..a4ba12936 100644 --- a/src/hooks/useSubmitTxn.ts +++ b/src/hooks/useSubmitTxn.ts @@ -28,6 +28,8 @@ import { } from '../types/solana' import { useSolana } from '../solana/SolanaProvider' import { useWalletSign } from '../solana/WalletSignProvider' +import BN from 'bn.js' +import { chunks } from '@helium/spl-utils' export default () => { const { currentAccount } = useAccountStorage() @@ -41,9 +43,10 @@ export default () => { async ( payments: { payee: string - balanceAmount: Balance + balanceAmount: BN max?: boolean }[], + mint: PublicKey, ) => { if ( !currentAccount?.solanaAddress || @@ -53,28 +56,28 @@ export default () => { throw new Error(t('errors.account')) } - const [firstPayment] = payments - const mintAddress = - firstPayment.balanceAmount.type.ticker !== 'SOL' - ? toMintAddress(firstPayment.balanceAmount.type.ticker, Mints) - : undefined - const paymentTxn = await solUtils.transferToken( - anchorProvider, - currentAccount.solanaAddress, - currentAccount.address, - payments, - mintAddress, + const txns = await Promise.all( + chunks(payments, 5).map((p) => { + return solUtils.transferToken( + anchorProvider, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + currentAccount.solanaAddress!, + currentAccount.address, + p, + mint, + ) + }), ) - const serializedTx = paymentTxn.serialize({ - requireAllSignatures: false, - }) - const decision = await walletSignBottomSheetRef.show({ type: WalletStandardMessageTypes.signTransaction, url: '', additionalMessage: t('transactions.signPaymentTxn'), - serializedTxs: [Buffer.from(serializedTx)], + serializedTxs: txns.map((tx) => + tx.serialize({ + requireAllSignatures: false, + }), + ), }) if (!decision) { @@ -83,7 +86,7 @@ export default () => { dispatch( makePayment({ - paymentTxn, + paymentTxns: txns, account: currentAccount, cluster, anchorProvider, diff --git a/src/locales/en.ts b/src/locales/en.ts index 3db9cc0e7..095354c13 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -399,6 +399,7 @@ export default { }, accountTokenList: { tokens: 'Tokens', + manage: 'Manage Visible Tokens', }, accountView: { balance: 'Balance', diff --git a/src/solana/SolanaProvider.tsx b/src/solana/SolanaProvider.tsx index 304904d35..6945fd653 100644 --- a/src/solana/SolanaProvider.tsx +++ b/src/solana/SolanaProvider.tsx @@ -1,30 +1,31 @@ +import { AnchorProvider, Wallet } from '@coral-xyz/anchor' +import { AccountFetchCache } from '@helium/account-fetch-cache' +import { init as initDc } from '@helium/data-credits-sdk' +import { init as initHem } from '@helium/helium-entity-manager-sdk' +import { init as initHsd } from '@helium/helium-sub-daos-sdk' +import { init as initLazy } from '@helium/lazy-distributor-sdk' +import { DC_MINT, HNT_MINT } from '@helium/spl-utils' +import { Cluster, Transaction } from '@solana/web3.js' import React, { - createContext, ReactNode, - useContext, + createContext, useCallback, - useState, - useRef, + useContext, useEffect, + useRef, + useState, } from 'react' -import { init as initHsd } from '@helium/helium-sub-daos-sdk' -import { init as initDc } from '@helium/data-credits-sdk' -import { init as initHem } from '@helium/helium-entity-manager-sdk' -import { init as initLazy } from '@helium/lazy-distributor-sdk' -import { AnchorProvider, Wallet } from '@coral-xyz/anchor' import Config from 'react-native-config' import { useSelector } from 'react-redux' -import { Cluster, Transaction } from '@solana/web3.js' -import { AccountFetchCache } from '@helium/account-fetch-cache' +import usePrevious from '../hooks/usePrevious' import { useAccountStorage } from '../storage/AccountStorageProvider' import { getSessionKey, getSolanaKeypair } from '../storage/secureStorage' -import { getConnection } from '../utils/solanaUtils' import { RootState } from '../store/rootReducer' import { appSlice } from '../store/slices/appSlice' import { useAppDispatch } from '../store/store' -import usePrevious from '../hooks/usePrevious' -import { WrappedConnection } from '../utils/WrappedConnection' import { DcProgram, HemProgram, HsdProgram, LazyProgram } from '../types/solana' +import { WrappedConnection } from '../utils/WrappedConnection' +import { getConnection } from '../utils/solanaUtils' const useSolanaHook = () => { const { currentAccount } = useAccountStorage() @@ -114,6 +115,13 @@ const useSolanaHook = () => { extendConnection: true, }), ) + // Don't sub to hnt or dc they change a bunch + cache?.statics.add(HNT_MINT.toBase58()) + cache?.statics.add(DC_MINT.toBase58()) + + return () => { + cache?.close() + } }, [ cache, cluster, diff --git a/src/storage/cloudStorage.ts b/src/storage/cloudStorage.ts index ce26a3bc9..253d1496c 100644 --- a/src/storage/cloudStorage.ts +++ b/src/storage/cloudStorage.ts @@ -21,6 +21,8 @@ export type CSAccount = { } export type CSAccounts = Record +export type CSToken = Record + // for android we use AsyncStorage and auto backup to Google Drive using // https://developer.android.com/guide/topics/data/autobackup const CloudStorage = Platform.OS === 'ios' ? iCloudStorage : AsyncStorage @@ -29,9 +31,21 @@ enum CloudStorageKeys { ACCOUNTS = 'accounts', CONTACTS = 'contacts', LAST_VIEWED_NOTIFICATIONS = 'lastViewedNotifications', + VISIBLE_TOKENS = 'visibleTokens', DEFAULT_ACCOUNT_ADDRESS = 'defaultAccountAddress', } +export const restoreVisibleTokens = async () => { + const tokens = await getFromCloudStorage( + CloudStorageKeys.VISIBLE_TOKENS, + ) + + return tokens +} + +export const updateVisibleTokens = (tokens: CSToken) => + CloudStorage.setItem(CloudStorageKeys.VISIBLE_TOKENS, JSON.stringify(tokens)) + export const sortAccounts = ( accts: CSAccounts, defaultAddress: string | undefined | null, diff --git a/src/store/slices/balancesSlice.ts b/src/store/slices/balancesSlice.ts index 91f6c55d3..35aaa539b 100644 --- a/src/store/slices/balancesSlice.ts +++ b/src/store/slices/balancesSlice.ts @@ -1,7 +1,7 @@ import { AnchorProvider } from '@coral-xyz/anchor' import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { Cluster, PublicKey } from '@solana/web3.js' -import { AccountLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { AccountLayout, TOKEN_PROGRAM_ID, getMint } from '@solana/spl-token' import BN from 'bn.js' import { CSAccount } from '../../storage/cloudStorage' import { getBalanceHistory, getTokenPrices } from '../../utils/walletApiV2' @@ -60,16 +60,20 @@ export const syncTokenAccounts = createAsyncThunk( programId: TOKEN_PROGRAM_ID, }) - const atas = tokenAccounts.value.map((tokenAccount) => { - const accountData = AccountLayout.decode(tokenAccount.account.data) - const { mint } = accountData - - return { - tokenAccount: tokenAccount.pubkey.toBase58(), - mint: mint.toBase58(), - balance: Number(accountData.amount || 0), - } - }) + const atas = await Promise.all( + tokenAccounts.value.map(async (tokenAccount) => { + const accountData = AccountLayout.decode(tokenAccount.account.data) + const { mint } = accountData + const mintAcc = await getMint(connection, mint) + + return { + tokenAccount: tokenAccount.pubkey.toBase58(), + mint: mint.toBase58(), + balance: Number(accountData.amount || 0), + decimals: mintAcc.decimals, + } + }), + ) const escrowAccount = getEscrowTokenAccount(acct.solanaAddress) let escrowBalance = 0 diff --git a/src/store/slices/solanaSlice.ts b/src/store/slices/solanaSlice.ts index f1af96337..75106a7d0 100644 --- a/src/store/slices/solanaSlice.ts +++ b/src/store/slices/solanaSlice.ts @@ -1,7 +1,7 @@ import { AnchorProvider } from '@coral-xyz/anchor' -import { Ticker } from '@helium/currency' import { bulkSendRawTransactions, + bulkSendTransactions, sendAndConfirmWithRetry, } from '@helium/spl-utils' import { @@ -17,14 +17,13 @@ import { import { first, last } from 'lodash' import { CSAccount } from '../../storage/cloudStorage' import { Activity } from '../../types/activity' -import { toMintAddress } from '../../types/solana' import * as Logger from '../../utils/logger' import * as solUtils from '../../utils/solanaUtils' import { postPayment } from '../../utils/walletApiV2' import { fetchCollectables } from './collectablesSlice' import { fetchHotspots } from './hotspotsSlice' -type TokenActivity = Record +type TokenActivity = Record type SolActivity = { all: TokenActivity @@ -56,7 +55,7 @@ type PaymentInput = { account: CSAccount cluster: Cluster anchorProvider: AnchorProvider - paymentTxn: Transaction + paymentTxns: Transaction[] } type CollectablePaymentInput = { @@ -120,21 +119,19 @@ type UpdateMobileInfoInput = { export const makePayment = createAsyncThunk( 'solana/makePayment', - async ({ account, cluster, anchorProvider, paymentTxn }: PaymentInput) => { + async ({ account, cluster, anchorProvider, paymentTxns }: PaymentInput) => { if (!account?.solanaAddress) throw new Error('No solana account found') - const signed = await anchorProvider.wallet.signTransaction(paymentTxn) + const signatures = await bulkSendTransactions(anchorProvider, paymentTxns) - const signature = await anchorProvider.sendAndConfirm(signed) - - postPayment({ signature, cluster }) + postPayment({ signatures, cluster }) postPayment({ - signature, + signatures, cluster, }) - return signature + return signatures }, ) @@ -151,7 +148,7 @@ export const makeCollectablePayment = createAsyncThunk( const sig = await anchorProvider.sendAndConfirm(signed) - postPayment({ signature: sig, cluster }) + postPayment({ signatures: [sig], cluster }) dispatch( fetchCollectables({ @@ -177,7 +174,7 @@ export const sendTreasurySwap = createAsyncThunk( const sig = await anchorProvider.sendAndConfirm(signed) - postPayment({ signature: sig, cluster }) + postPayment({ signatures: [sig], cluster }) } catch (error) { Logger.error(error) throw error @@ -194,7 +191,7 @@ export const sendMintDataCredits = createAsyncThunk( const sig = await anchorProvider.sendAndConfirm(signed) - postPayment({ signature: sig, cluster }) + postPayment({ signatures: [sig], cluster }) } catch (error) { Logger.error(error) throw error @@ -215,7 +212,7 @@ export const sendDelegateDataCredits = createAsyncThunk( const sig = await anchorProvider.sendAndConfirm(signed) - postPayment({ signature: sig, cluster }) + postPayment({ signatures: [sig], cluster }) } catch (error) { Logger.error(error) throw error @@ -241,7 +238,7 @@ export const sendAnchorTxn = createAsyncThunk( 'confirmed', ) - postPayment({ signature: txid, cluster }) + postPayment({ signatures: [txid], cluster }) } catch (error) { Logger.error(error) throw error @@ -259,18 +256,18 @@ export const claimRewards = createAsyncThunk( try { const signed = await anchorProvider.wallet.signAllTransactions(txns) - const sigs = await bulkSendRawTransactions( + const signatures = await bulkSendRawTransactions( anchorProvider.connection, signed.map((s) => s.serialize()), ) - postPayment({ signature: sigs[0], cluster }) + postPayment({ signatures, cluster }) // If the transfer is successful, we need to update the hotspots so pending rewards are updated. dispatch(fetchHotspots({ account, anchorProvider, cluster })) return { - signature: sigs[0], + signatures, } } catch (error) { Logger.error(error) @@ -309,14 +306,12 @@ export const getTxns = createAsyncThunk( { account, anchorProvider, - ticker, + mint, requestType, - mints, }: { account: CSAccount anchorProvider: AnchorProvider - ticker: Ticker - mints: Record + mint: string requestType: 'update_head' | 'start_fresh' | 'fetch_more' }, { getState }, @@ -331,7 +326,7 @@ export const getTxns = createAsyncThunk( solana: SolanaState } - const existing = solana.activity.data[account.solanaAddress]?.all?.[ticker] + const existing = solana.activity.data[account.solanaAddress]?.all?.[mint] if (requestType === 'fetch_more') { const lastActivity = last(existing) @@ -351,8 +346,7 @@ export const getTxns = createAsyncThunk( return solUtils.getTransactions( anchorProvider, account.solanaAddress, - toMintAddress(ticker, mints), - mints, + mint, options, ) }, @@ -365,7 +359,7 @@ export const sendUpdateIotInfo = createAsyncThunk( const signed = await anchorProvider.wallet.signTransaction(updateTxn) const sig = await anchorProvider.sendAndConfirm(signed) - postPayment({ signature: sig, cluster }) + postPayment({ signatures: [sig], cluster }) } catch (error) { Logger.error(error) throw error @@ -381,7 +375,7 @@ export const sendUpdateMobileInfo = createAsyncThunk( const signed = await anchorProvider.wallet.signTransaction(updateTxn) const sig = await anchorProvider.sendAndConfirm(signed) - postPayment({ signature: sig, cluster }) + postPayment({ signatures: [sig], cluster }) } catch (error) { Logger.error(error) throw error @@ -454,12 +448,12 @@ const solanaSlice = createSlice({ } }) builder.addCase(claimRewards.fulfilled, (state, _action) => { - const { signature } = _action.payload + const { signatures } = _action.payload state.payment = { success: true, loading: false, error: undefined, - signature, + signature: signatures[0], } }) builder.addCase(sendAnchorTxn.rejected, (state, action) => { @@ -591,7 +585,7 @@ const solanaSlice = createSlice({ if (!meta.arg.account.solanaAddress) return const { - ticker, + mint, account: { solanaAddress: address }, requestType, } = meta.arg @@ -615,47 +609,46 @@ const solanaSlice = createSlice({ if (!state.activity.data[address].mint) { state.activity.data[address].mint = state.activity.data[address].all } - - const prevAll = state.activity.data[address].all[ticker] - const prevPayment = state.activity.data[address].payment[ticker] - const prevDelegate = state.activity.data[address].delegate[ticker] - const prevMint = state.activity.data[address].mint[ticker] + const prevAll = state.activity.data[address].all[mint] + const prevPayment = state.activity.data[address].payment[mint] + const prevDelegate = state.activity.data[address].delegate[mint] + const prevMint = state.activity.data[address].mint[mint] switch (requestType) { case 'start_fresh': { - state.activity.data[address].all[ticker] = payload - state.activity.data[address].payment[ticker] = payload - state.activity.data[address].delegate[ticker] = payload - state.activity.data[address].mint[ticker] = payload + state.activity.data[address].all[mint] = payload + state.activity.data[address].payment[mint] = payload + state.activity.data[address].delegate[mint] = payload + state.activity.data[address].mint[mint] = payload break } case 'fetch_more': { - state.activity.data[address].all[ticker] = [...prevAll, ...payload] - state.activity.data[address].payment[ticker] = [ + state.activity.data[address].all[mint] = [...prevAll, ...payload] + state.activity.data[address].payment[mint] = [ ...prevPayment, ...payload, ] - state.activity.data[address].delegate[ticker] = [ + state.activity.data[address].delegate[mint] = [ ...prevDelegate, ...payload, ] - state.activity.data[address].mint[ticker] = [ + state.activity.data[address].mint[mint] = [ ...prevDelegate, ...payload, ] break } case 'update_head': { - state.activity.data[address].all[ticker] = [...payload, ...prevAll] - state.activity.data[address].payment[ticker] = [ + state.activity.data[address].all[mint] = [...payload, ...prevAll] + state.activity.data[address].payment[mint] = [ ...payload, ...prevPayment, ] - state.activity.data[address].delegate[ticker] = [ + state.activity.data[address].delegate[mint] = [ ...payload, ...prevDelegate, ] - state.activity.data[address].mint[ticker] = [...payload, ...prevMint] + state.activity.data[address].mint[mint] = [...payload, ...prevMint] break } } diff --git a/src/types/activity.ts b/src/types/activity.ts index 146846559..a9ed41efa 100644 --- a/src/types/activity.ts +++ b/src/types/activity.ts @@ -1,5 +1,3 @@ -import { Ticker } from '@helium/currency' - export type Activity = { account?: null | string address?: null | string @@ -34,7 +32,7 @@ export type Activity = { stakingFee?: null | number startEpoch?: null | number time?: null | number - tokenType?: null | Ticker + mint?: null | string type: string } @@ -42,7 +40,7 @@ export type Payment = { amount: number memo?: null | string payee: string - tokenType?: null | Ticker + mint?: null | string } export type Reward = { diff --git a/src/types/balance.ts b/src/types/balance.ts index 1033b1c83..636c738ae 100644 --- a/src/types/balance.ts +++ b/src/types/balance.ts @@ -10,6 +10,7 @@ export type TokenAccount = { tokenAccount?: string mint: string balance: number + decimals: number } export type AccountBalance = { diff --git a/src/utils/Balance.tsx b/src/utils/Balance.tsx index 37660a300..8870e1c99 100644 --- a/src/utils/Balance.tsx +++ b/src/utils/Balance.tsx @@ -325,6 +325,8 @@ const useBalanceHook = () => { return } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore setBalanceInfo(tokenInfo) }, [cluster, prevCluster, prevSolAddress, solanaAddress, tokenInfo]) diff --git a/src/utils/linking.ts b/src/utils/linking.ts index 8e352ccd7..3ad052907 100644 --- a/src/utils/linking.ts +++ b/src/utils/linking.ts @@ -1,15 +1,11 @@ import Address from '@helium/address' -import Balance, { - CurrencyType, - NetworkTokens, - TestNetworkTokens, - Ticker, -} from '@helium/currency' +import Balance, { CurrencyType } from '@helium/currency' +import { LinkingOptions } from '@react-navigation/native' +import BigNumber from 'bignumber.js' +import BN from 'bn.js' import * as Linking from 'expo-linking' import qs from 'qs' import queryString from 'query-string' -import BigNumber from 'bignumber.js' -import { LinkingOptions } from '@react-navigation/native' import { BurnRouteParam, PaymentRouteParam } from '../features/home/homeTypes' import { RootStackParamList } from '../navigation/rootTypes' import { useAccountStorage } from '../storage/AccountStorageProvider' @@ -20,7 +16,7 @@ export const HELIUM_WALLET_LINK_SCHEME = 'https://wallet.helium.com/' export type SendDetails = { payee: string - balanceAmount: Balance | Balance + balanceAmount: BN max?: boolean } @@ -67,46 +63,22 @@ export const useDeepLinking = () => { export const makePayRequestLink = ({ payee, balanceAmount, - defaultTokenType, -}: Partial & { defaultTokenType?: Ticker }) => { + mint, +}: Partial & { mint?: string }) => { return [ HELIUM_WALLET_LINK_SCHEME + PAYMENT_PATH, qs.stringify( { payee, - amount: balanceAmount?.integerBalance || null, + amount: balanceAmount?.toString(), memo: '', - defaultTokenType, + mint, }, { skipNulls: true }, ), ].join('?') } -export const makeMultiPayRequestLink = ({ - payments, - payer, -}: { - payer?: string - payments: Array & { defaultTokenType?: Ticker }> -}) => { - const ironed = payments.map( - ({ payee: address, balanceAmount, defaultTokenType }) => ({ - payee: address || null, - amount: balanceAmount?.integerBalance || null, - memo: '', - defaultTokenType, - }), - ) - return [ - HELIUM_WALLET_LINK_SCHEME + PAYMENT_PATH, - qs.stringify( - { payer, payments: JSON.stringify(ironed) }, - { skipNulls: true }, - ), - ].join('?') -} - export const parseBurn = (qrContent: string) => { try { const parsedJson = JSON.parse(qrContent) diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index b2dcb4199..3a43588a6 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -1,126 +1,123 @@ /* eslint-disable no-underscore-dangle */ -import { - Cluster, - clusterApiUrl, - ConfirmedSignatureInfo, - Connection, - Keypair, - LAMPORTS_PER_SOL, - Logs, - PublicKey, - SignaturesForAddressOptions, - Signer, - SystemProgram, - TransactionInstruction, - TransactionMessage, - VersionedMessage, - VersionedTransaction, - VersionedTransactionResponse, - ComputeBudgetProgram, - AccountMeta, - SignatureResult, - ParsedTransactionWithMeta, - ParsedInstruction, - Transaction, -} from '@solana/web3.js' +import { AnchorProvider, BN } from '@coral-xyz/anchor' import * as dc from '@helium/data-credits-sdk' -import { subDaoKey } from '@helium/helium-sub-daos-sdk' import { - TOKEN_PROGRAM_ID, - AccountLayout, - createTransferCheckedInstruction, - getOrCreateAssociatedTokenAccount, - getAssociatedTokenAddress, - getMint, - getAssociatedTokenAddressSync, - getAccount, - ASSOCIATED_TOKEN_PROGRAM_ID, - createAssociatedTokenAccountInstruction, - createAssociatedTokenAccountIdempotentInstruction, -} from '@solana/spl-token' + delegatedDataCreditsKey, + escrowAccountKey, +} from '@helium/data-credits-sdk' +import { + formBulkTransactions, + getBulkRewards, + getPendingRewards, +} from '@helium/distributor-oracle' +import { + PROGRAM_ID as FanoutProgramId, + fanoutKey, + membershipCollectionKey, +} from '@helium/fanout-sdk' import { - init as initHem, entityCreatorKey, - rewardableEntityConfigKey, - updateMobileMetadata, - updateIotMetadata, - keyToAssetKey, + init as initHem, iotInfoKey, + keyToAssetKey, mobileInfoKey, + rewardableEntityConfigKey, + updateIotMetadata, + updateMobileMetadata, } from '@helium/helium-entity-manager-sdk' -import Balance, { AnyCurrencyType, CurrencyType } from '@helium/currency' -import { JsonMetadata, Metadata, Metaplex } from '@metaplex-foundation/js' -import axios from 'axios' -import Config from 'react-native-config' -import { - TreeConfig, - createTransferInstruction, - PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, -} from '@metaplex-foundation/mpl-bubblegum' -import { - ConcurrentMerkleTreeAccount, - SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - SPL_NOOP_PROGRAM_ID, -} from '@solana/spl-account-compression' -import bs58 from 'bs58' +import { subDaoKey } from '@helium/helium-sub-daos-sdk' +import * as lz from '@helium/lazy-distributor-sdk' +import { HotspotType } from '@helium/onboarding' import { Asset, + DC_MINT, HNT_MINT, + IOT_MINT, + MOBILE_MINT, getAsset, searchAssets, - toBN, sendAndConfirmWithRetry, - IOT_MINT, - DC_MINT, - MOBILE_MINT, + toBN, truthy, } from '@helium/spl-utils' -import { AnchorProvider, BN } from '@coral-xyz/anchor' import * as tm from '@helium/treasury-management-sdk' -import { - delegatedDataCreditsKey, - escrowAccountKey, -} from '@helium/data-credits-sdk' -import { - getPendingRewards, - getBulkRewards, - formBulkTransactions, -} from '@helium/distributor-oracle' -import * as lz from '@helium/lazy-distributor-sdk' -import { - PROGRAM_ID as FanoutProgramId, - fanoutKey, - membershipCollectionKey, -} from '@helium/fanout-sdk' import { PROGRAM_ID as VoterStakeRegistryProgramId, - registrarKey, registrarCollectionKey, + registrarKey, } from '@helium/voter-stake-registry-sdk' -import { BaseCurrencyType } from '@helium/currency/build/currency_types' -import { HotspotType } from '@helium/onboarding' +import { JsonMetadata, Metadata, Metaplex } from '@metaplex-foundation/js' +import { + PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, + TreeConfig, + createTransferInstruction, +} from '@metaplex-foundation/mpl-bubblegum' +import { + ConcurrentMerkleTreeAccount, + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + SPL_NOOP_PROGRAM_ID, +} from '@solana/spl-account-compression' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + AccountLayout, + TOKEN_PROGRAM_ID, + createAssociatedTokenAccountIdempotentInstruction, + createAssociatedTokenAccountInstruction, + createTransferCheckedInstruction, + getAccount, + getAssociatedTokenAddress, + getAssociatedTokenAddressSync, + getMint, + getOrCreateAssociatedTokenAccount, +} from '@solana/spl-token' +import { + AccountMeta, + Cluster, + ComputeBudgetProgram, + ConfirmedSignatureInfo, + Connection, + Keypair, + LAMPORTS_PER_SOL, + Logs, + ParsedInstruction, + ParsedTransactionWithMeta, + PublicKey, + SignatureResult, + SignaturesForAddressOptions, + Signer, + SystemProgram, + Transaction, + TransactionInstruction, + TransactionMessage, + VersionedMessage, + VersionedTransaction, + VersionedTransactionResponse, + clusterApiUrl, +} from '@solana/web3.js' +import axios from 'axios' +import bs58 from 'bs58' +import Config from 'react-native-config' import { getKeypair, getSessionKey } from '../storage/secureStorage' import { Activity, Payment } from '../types/activity' -import sleep from './sleep' import { Collectable, CompressedNFT, EnrichedTransaction, HotspotWithPendingRewards, - mintToTicker, } from '../types/solana' -import * as Logger from './logger' import { WrappedConnection } from './WrappedConnection' +import { solAddressIsValid } from './accountUtils' import { DAO_KEY, IOT_LAZY_KEY, IOT_SUB_DAO_KEY, - Mints, MOBILE_LAZY_KEY, MOBILE_SUB_DAO_KEY, + Mints, } from './constants' -import { solAddressIsValid } from './accountUtils' import { getH3Location } from './h3' +import * as Logger from './logger' +import sleep from './sleep' const govProgramId = new PublicKey( 'hgovkRU6Ghe1Qoyb54HdSLdqN7VtxaifBzRmh9jtd3S', @@ -195,7 +192,7 @@ export const createTransferSolTxn = async ( signer: Signer, payments: { payee: string - balanceAmount: Balance + balanceAmount: BN max?: boolean }[], ) => { @@ -205,12 +202,12 @@ export const createTransferSolTxn = async ( let instructions: TransactionInstruction[] = [] payments.forEach((p) => { - const amount = p.balanceAmount.integerBalance + const amount = p.balanceAmount const instruction = SystemProgram.transfer({ fromPubkey: payer, toPubkey: new PublicKey(p.payee), - lamports: amount, + lamports: BigInt(amount.toString()), }) instructions = [...instructions, instruction] @@ -235,7 +232,7 @@ export const createTransferTxn = async ( signer: Signer, payments: { payee: string - balanceAmount: Balance + balanceAmount: BN max?: boolean }[], mintAddress: string, @@ -244,11 +241,10 @@ export const createTransferTxn = async ( const conn = anchorProvider.connection - const [firstPayment] = payments - const payer = signer.publicKey const mint = new PublicKey(mintAddress) + const mintAcc = await getMint(conn, mint) const payerATA = await getOrCreateAssociatedTokenAccount( conn, @@ -259,7 +255,7 @@ export const createTransferTxn = async ( let instructions: TransactionInstruction[] = [] payments.forEach((p) => { - const amount = p.balanceAmount.integerBalance + const amount = p.balanceAmount const ata = getAssociatedTokenAddressSync(mint, new PublicKey(p.payee)) instructions = [ @@ -275,8 +271,8 @@ export const createTransferTxn = async ( mint, ata, payer, - amount, - firstPayment.balanceAmount.type.decimalPlaces.toNumber(), + BigInt(amount.toString()), + mintAcc.decimals, [signer], ), ] @@ -302,7 +298,7 @@ export const transferToken = async ( heliumAddress: string, payments: { payee: string - balanceAmount: Balance + balanceAmount: BN max?: boolean }[], mintAddress?: string, @@ -362,7 +358,6 @@ export const getTransactions = async ( anchorProvider: AnchorProvider, walletAddress: string, mintAddress: string, - mints: typeof Mints, options?: SignaturesForAddressOptions, ) => { try { @@ -376,7 +371,7 @@ export const getTransactions = async ( }) return transactionDetails - .map((td, idx) => solInstructionsToActivity(td, sigs[idx], mints)) + .map((td, idx) => solInstructionsToActivity(td, sigs[idx])) .filter((a) => !!a) as Activity[] } catch (e) { Logger.error(e) @@ -581,9 +576,9 @@ export const getAtaAccountCreationFee = async ({ try { await getAccount(connection, ataAddress) - return new Balance(0, CurrencyType.solTokens) + return new BN(0) } catch { - return Balance.fromFloat(0.00203928, CurrencyType.solTokens) + return new BN(0.00203928 * LAMPORTS_PER_SOL) } } @@ -1415,7 +1410,6 @@ export async function createTreasurySwapMessage( export const solInstructionsToActivity = ( parsedTxn: ParsedTransactionWithMeta | null, signature: string, - mints: typeof Mints, ) => { if (!parsedTxn) return @@ -1444,14 +1438,14 @@ export const solInstructionsToActivity = ( if (amount < 0) { // is payer activity.payer = post.owner - activity.tokenType = mintToTicker(post.mint, mints) + activity.mint = post.mint activity.amount = -1 * amount } else { // is payee const p: Payment = { amount, payee: post.owner || '', - tokenType: mintToTicker(post.mint, mints), + mint: post.mint, } payments = [...payments, p] } @@ -1472,10 +1466,10 @@ export const solInstructionsToActivity = ( if (activity.type === 'unknown') return const payment = activity.payments?.[0] - if (payment && payment.tokenType === 'DC') { + if (payment && payment.mint === DC_MINT.toBase58()) { activity.type = payment.payee !== activity.payer ? 'dc_delegate' : 'dc_mint' activity.amount = payment.amount - activity.tokenType = 'DC' + activity.mint = payment.mint } return activity @@ -1499,11 +1493,8 @@ export const submitSolana = async ({ return txid } -export const parseTransactionError = ( - balance?: Balance, - message?: string, -) => { - if ((balance?.floatBalance || 0) > 0.02) { +export const parseTransactionError = (balance?: BN, message?: string) => { + if (balance?.gt(new BN(0.02))) { return 'The SOL balance on this account is too low to complete this transaction' } @@ -1531,19 +1522,19 @@ export const calcCreateAssociatedTokenAccountAccountFee = async ( provider: AnchorProvider, payee: string, mint: PublicKey, -): Promise> => { +): Promise => { if (!payee) { - return new Balance(0, CurrencyType.solTokens) + return new BN(0) } if (!solAddressIsValid(payee)) { - return new Balance(0, CurrencyType.solTokens) + return new BN(0) } const payeePubKey = new PublicKey(payee) const ata = await getAssociatedTokenAddress(mint, payeePubKey) if (ata) { - return new Balance(0, CurrencyType.solTokens) + return new BN(0) } const transaction = new Transaction().add( @@ -1566,11 +1557,11 @@ export const calcCreateAssociatedTokenAccountAccountFee = async ( const fee = await transaction.getEstimatedFee(provider.connection) if (!fee) { - return new Balance(0, CurrencyType.solTokens) + return new BN(0) } - return new Balance(fee, CurrencyType.solTokens) + return new BN(fee) } catch (e) { - return new Balance(0, CurrencyType.solTokens) + return new BN(0) } } diff --git a/src/utils/walletApiV2.ts b/src/utils/walletApiV2.ts index ff6185b88..91a6ad675 100644 --- a/src/utils/walletApiV2.ts +++ b/src/utils/walletApiV2.ts @@ -114,14 +114,14 @@ export const postNotificationRead = async ({ } export const postPayment = async ({ - signature, + signatures, cluster, }: { - signature: string + signatures: string[] cluster: Cluster }) => { const url = `/payments?cluster=${cluster}` - return axiosInstance.post(url, { signature }) + return axiosInstance.post(url, { signature: signatures[0], signatures }) } export const getRecommendedDapps = async () => { diff --git a/yarn.lock b/yarn.lock index d9d8983c5..65e36f3cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2259,6 +2259,22 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@helium/account-fetch-cache-hooks@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache-hooks/-/account-fetch-cache-hooks-0.2.14.tgz#78ead8cb09f39c13067089dd14c9d208c7d344e2" + integrity sha512-jD2YOjmbUyZEznUJ0Z9ojrmvgAP8+nbH9aoFq1a5ENZcSp0+B9XEWw7XQ+KDTrECw51vpSsUrtxFJYj3WjG4PA== + dependencies: + "@helium/account-fetch-cache" "^0.2.14" + "@solana/web3.js" "^1.66.2" + react-async-hook "^4.0.0" + +"@helium/account-fetch-cache@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.14.tgz#9112da0635ffd2b5145d10c85beedc13d65a62b8" + integrity sha512-jPfpe2ZpXP2n7K++JlXEFK8iAT3wMGdNAaB1vGv/vG5twOvW+bgDfD6oW6qFt0hOywnIYIdjQe4cEsYSOYiizA== + dependencies: + "@solana/web3.js" "^1.43.4" + "@helium/account-fetch-cache@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.5.tgz#518e945abd51bad1811ff0b4b5c80d62ebafdb6f" @@ -2275,6 +2291,15 @@ js-sha256 "^0.9.0" multiformats "^9.6.4" +"@helium/address@^4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.10.2.tgz#56960b118fceb6b6ddabe3e4ecec467d9ae50e26" + integrity sha512-qCswC7Z3GXuJyHv36RcOSnffeghjqJQx0fdu2Lxpf9fgOnIi1JZO2tjjk1mBaqOwCyp+0YzrTPUoEukL/WCtsA== + dependencies: + bs58 "^5.0.0" + js-sha256 "^0.9.0" + multiformats "^9.6.4" + "@helium/address@^4.6.2", "@helium/address@^4.8.0", "@helium/address@^4.8.1": version "4.8.1" resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.8.1.tgz#d8d7cefc6aa7791d79eb8759befb821aaccec3ff" @@ -2304,10 +2329,10 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/circuit-breaker-sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.5.tgz#5124fa950f706b324eb53ffc3b718a6ecf5cdd76" - integrity sha512-IG/f0ffY+lWPVOAYTH/yyloXRfCShUydwsyAjdYz4tI1tGVlysf88rR9kYWY/cJpep3ftemxZdvOwOSdhVt72A== +"@helium/circuit-breaker-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.14.tgz#a325cf881f8dec6b7f398a4a08f8a74e70610d63" + integrity sha512-Y9BLVw4oJNd82oJWYYa3JpsRsnT7FE39j00Q5ZuIuYDcDHZWdOYBxZCZT/y3wEc20P0MQpDC2n7yMwvI3imXmA== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/anchor-resolvers" "^0.2.5" @@ -2348,36 +2373,37 @@ dependencies: bignumber.js "^9.0.0" -"@helium/data-credits-sdk@0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@helium/data-credits-sdk/-/data-credits-sdk-0.1.2.tgz#eefb1437bba789f5a4490d9567491a1091f148bc" - integrity sha512-S5zgjbZ/yMQwiHZmixFj+o1gzVsCu07WcrwW0brbBTrPXcMjwyDOfSxLfEmBPRLku/V6DXZjeNYiTmrFJGet2A== +"@helium/data-credits-sdk@0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/data-credits-sdk/-/data-credits-sdk-0.2.14.tgz#0ee3984fb672c03a200c8ca43e7f0947e7f890dc" + integrity sha512-pg+jY+EcdDkaOoI0BFbwl4t5HiZkxGN++s0JDU1vP+Vy/qYJ+vx9HwlXGvVZlyE6UV8naJN4+c0C5zStwJdjfQ== dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.2" - "@helium/helium-sub-daos-sdk" "^0.1.2" - "@helium/idls" "^0.1.1" - "@helium/spl-utils" "^0.1.2" - "@solana/spl-token" "^0.3.6" + "@coral-xyz/anchor" "^0.26.0" + "@helium/anchor-resolvers" "^0.2.5" + "@helium/circuit-breaker-sdk" "^0.2.14" + "@helium/helium-sub-daos-sdk" "^0.2.14" + "@helium/idls" "^0.2.5" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" -"@helium/distributor-oracle@^0.2.6": - version "0.2.6" - resolved "https://registry.yarnpkg.com/@helium/distributor-oracle/-/distributor-oracle-0.2.6.tgz#db9bbbe485fec8192a8d85f6e5681a14d4c985c3" - integrity sha512-s8aeRBOR4W8KG6ORg0I+VsJveKTCnLTscP/puY20Rv41Gtb1vsUH/QwSYGVFlWueuWaS42vAIE5/STkg95c2xw== +"@helium/distributor-oracle@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/distributor-oracle/-/distributor-oracle-0.2.14.tgz#677dccd5167333adea2cc2d777c66d677b330568" + integrity sha512-2ecSvRYVvWPh48mkh6mLnCgghlZVDcFDHE/KLKermCR5HUFrATFuNmFGyDMMCzUZ4qufM915iK76X1/SgdyCsA== dependencies: "@coral-xyz/anchor" "^0.26.0" "@fastify/cors" "^8.1.1" - "@helium/account-fetch-cache" "^0.2.5" - "@helium/address" "^4.6.2" - "@helium/helium-entity-manager-sdk" "^0.2.6" - "@helium/helium-sub-daos-sdk" "^0.2.5" + "@helium/account-fetch-cache" "^0.2.14" + "@helium/address" "^4.10.2" + "@helium/helium-entity-manager-sdk" "^0.2.14" + "@helium/helium-sub-daos-sdk" "^0.2.14" "@helium/idls" "^0.2.5" - "@helium/lazy-distributor-sdk" "^0.2.5" - "@helium/rewards-oracle-sdk" "^0.2.5" - "@helium/spl-utils" "^0.2.6" + "@helium/lazy-distributor-sdk" "^0.2.14" + "@helium/rewards-oracle-sdk" "^0.2.14" + "@helium/spl-utils" "^0.2.14" + "@metaplex-foundation/mpl-bubblegum" "^0.7.0" + "@solana/spl-token" "^0.3.8" "@types/sequelize" "^4.28.14" aws-sdk "^2.1313.0" axios "^0.27.2" @@ -2402,57 +2428,46 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/helium-entity-manager-sdk@^0.2.6": - version "0.2.6" - resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.2.6.tgz#6175ff12c2ba58923ab3a69f18158120de13c1af" - integrity sha512-Z5LvS/MtELlVH/fGf0n6k5UEdjUAQn6MEMU1oO91lj3ft3mqi7BVVMttESohTnsWi3tVgceTs/Rye8e07TfD9A== +"@helium/helium-entity-manager-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.2.14.tgz#f2a864797daf03a32f6da52f6d9ab56c8dc259c1" + integrity sha512-mWZ10jo1MxwpeB+Mw9JZ5kmRU6gYwJSHUisCuwsi/0OSnZI8l9QlRGsM3HrLA9RnuCeUD+2h+/56wdtF9esTmg== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/address" "^4.6.2" + "@helium/address" "^4.10.2" "@helium/anchor-resolvers" "^0.2.5" - "@helium/helium-sub-daos-sdk" "^0.2.5" + "@helium/helium-sub-daos-sdk" "^0.2.14" "@helium/idls" "^0.2.5" - "@helium/spl-utils" "^0.2.6" + "@helium/spl-utils" "^0.2.14" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" js-sha256 "^0.9.0" -"@helium/helium-react-hooks@0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@helium/helium-react-hooks/-/helium-react-hooks-0.1.2.tgz#ad609c3eee61de5d73507bcd68ff09eb8760dea2" - integrity sha512-36uINzAPlduQ+p1kF21JPgQMrpB4ECj9eBYnvdnppkXw3/dOH5KKNq4vkE1U7r7g8/Ymnyx6soVlGhUl8HHZ0w== +"@helium/helium-react-hooks@0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/helium-react-hooks/-/helium-react-hooks-0.2.14.tgz#82eead80f801ae9b0a46daca3fae38860d22dd35" + integrity sha512-vtkA6YVVoARm+WD50x7/jvUWg/s64KnIS+Gl2UVx6otsgtdPWKEM/iEAG4z/I6Ri1z/q+yLUOnjps5xuuKBToA== dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/spl-utils" "^0.1.2" + "@coral-xyz/anchor" "^0.26.0" + "@helium/account-fetch-cache" "^0.2.14" + "@helium/account-fetch-cache-hooks" "^0.2.14" "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.66.2" bs58 "^5.0.0" + pako "^2.0.3" react-async-hook "^4.0.0" -"@helium/helium-sub-daos-sdk@0.1.2", "@helium/helium-sub-daos-sdk@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.1.2.tgz#c0ea730f7b26d2f42c0d62c13caace67f8f8c023" - integrity sha512-n/ZwnDDSxniYgxvAom0qfzWk2xZMlN7mUEzrzt+3MwiNX3Qm0/khHjLv6MdgfXnCapunekEUl2xU6I7YpYxyOw== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.2" - "@helium/spl-utils" "^0.1.2" - "@helium/treasury-management-sdk" "^0.1.2" - "@helium/voter-stake-registry-sdk" "^0.1.2" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/helium-sub-daos-sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.2.5.tgz#1f9af001a3784250bce899a9ce4a35020da11dde" - integrity sha512-U+AT8t3wJhZTambCtSHnvXBl+5fVw2NhQ49AgbzKm3Yo3JBnyXJ31mdTGyeT67QrviOW2nOmU8adozp7AXsiOw== +"@helium/helium-sub-daos-sdk@0.2.14", "@helium/helium-sub-daos-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.2.14.tgz#8dd88525491a8f0504343d4b88ae1c9f5580abd3" + integrity sha512-TSadUwMVN9jD0aDN5t9n3S2b6X6qR+PVfbwZsCKXRoeEJ8Pi7qqeMkHbpH1dKz2B6UGGQWnLva6zsiIuaZoShw== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.5" - "@helium/treasury-management-sdk" "^0.2.5" - "@helium/voter-stake-registry-sdk" "^0.2.5" + "@helium/circuit-breaker-sdk" "^0.2.14" + "@helium/treasury-management-sdk" "^0.2.14" + "@helium/voter-stake-registry-sdk" "^0.2.14" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2515,14 +2530,14 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/lazy-distributor-sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.2.5.tgz#b01bbc57b87f5f24168ecdfaaf2af6e45d17cebe" - integrity sha512-pkG1jUbkGehiurO15ew4AEN0j0WaSQKk0AQNVEZoJy4TG3TFVuCJPr8gyN430nRuwdl5KZgs4gItD1cigw7/dw== +"@helium/lazy-distributor-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.2.14.tgz#5989317a2ef1aed0235b21dfdd73fa5307227604" + integrity sha512-+3hNVpwOV0PSBgysunEZYI7rHXB1fLq3n3BbVva7kb0gm0QAwJcoI2NPVn98jI3x1jCgQnw0SYAPUFAUiYV0nw== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.5" + "@helium/circuit-breaker-sdk" "^0.2.14" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2554,10 +2569,10 @@ resolved "https://registry.yarnpkg.com/@helium/react-native-sdk/-/react-native-sdk-1.0.0.tgz#41024fa99859490bd8a0b717f52acc11ae72f114" integrity sha512-Qi1Nnp/q2hsz2D7aeuM6LxXhNX8NrHz1U+PoQslwK2XfqPFZEYb4uAzjXDKlc+JBWPiF96GMJywv/ofxlZ9XLg== -"@helium/rewards-oracle-sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.2.5.tgz#24f0fa7cb5971264c4b5b99e89033ffeab3be000" - integrity sha512-eiOAMhNcQg6bAm/bbIKW3UM/TqVO38HUWJur8HdzCCjfxYtxXjtAozDggfePJAZ1WGe2oaReEBXId+KRkBPE5w== +"@helium/rewards-oracle-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.2.14.tgz#8422914271821b43850572da677866870adabdeb" + integrity sha512-VE4FJy43tvvIdEoJBHch2+BlQzeBaDdcxzOZPXftVM9gxL0sHxlAKRXsozgg6eRB3mowMKd6EHa4FxJztgMBMA== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/anchor-resolvers" "^0.2.5" @@ -2579,6 +2594,23 @@ borsh "^0.7.0" bs58 "^5.0.0" +"@helium/spl-utils@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.14.tgz#7d9bcc8236095d81c9cea661c9ca32741cb70bf6" + integrity sha512-joRHzlFppePOymHQ8GpAXYLfsfOZZ1t8k0OD0+c6ceuWUpx5N6Fu2YUu0Yv9Rj2nvFnl++G0DSmy1+Q8yvkOGA== + dependencies: + "@coral-xyz/anchor" "^0.26.0" + "@helium/account-fetch-cache" "^0.2.14" + "@helium/address" "^4.10.2" + "@helium/anchor-resolvers" "^0.2.5" + "@metaplex-foundation/mpl-token-metadata" "^2.5.2" + "@solana/spl-account-compression" "^0.1.7" + "@solana/spl-token" "^0.3.6" + "@solana/web3.js" "^1.43.4" + bn.js "^5.2.0" + borsh "^0.7.0" + bs58 "^5.0.0" + "@helium/spl-utils@^0.2.6": version "0.2.6" resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.6.tgz#20b136f13d9e1d58e7032b4c55b5cc0c1ad9f6dc" @@ -2607,7 +2639,7 @@ long "^4.0.0" path "^0.12.7" -"@helium/treasury-management-sdk@0.1.2", "@helium/treasury-management-sdk@^0.1.2": +"@helium/treasury-management-sdk@0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.1.2.tgz#abdb08548e05535eeee32a492238a821bd95e5fe" integrity sha512-rMazqYxFSxjn+tjQOLKbFORKZDqhfKX9OeHueZN1N+t2a4c839Nl495ai6crjQ0546MSHUx5M29XTVXRY5ceOA== @@ -2620,19 +2652,19 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/treasury-management-sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.5.tgz#fc3babc07a26da6bd75c9cd7fa9774e7803e4305" - integrity sha512-j1faBZ52KLAaCz5GHmK5Xugg7PvOi5kpP4PmB0DmTRdhF8f7mfTIPWvD/SVI1vIQ4p1ng8ucURKTpCWRGRzIqA== +"@helium/treasury-management-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.14.tgz#9a640c38f7e7de9e302c8fa3711b683735ca8285" + integrity sha512-KtOF2gkG0qe/81HSnLsJs/oYVRdNGJO7JZPmy9+Rrozt8c+3R0no4KqYRBG1I271ijEwYWpfwiyM8a8i7V6Dsw== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.5" + "@helium/circuit-breaker-sdk" "^0.2.14" "@helium/idls" "^0.2.5" bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/voter-stake-registry-sdk@0.1.2", "@helium/voter-stake-registry-sdk@^0.1.2": +"@helium/voter-stake-registry-sdk@0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.1.2.tgz#413d3e56b1746de9a4986dd503d19bc8dc958c13" integrity sha512-Xs2VOZaYnHgtA803HgJEiVJv6BeDyZxeJCapZBInVATO7Ry7cdnmaso8h5KW+3wI/wk3/LsyX8BvKIxUJk3nFQ== @@ -2645,10 +2677,10 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/voter-stake-registry-sdk@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.5.tgz#eefc2de7a4d77a016fefb63446816ccac8b9ff8c" - integrity sha512-+u87bpAHDZRosMEjqa3xTGSGEvFjrC6Mq6b0AqzMOAc0z3rYxNCRaSXRZ9fpOoGwm+hJfVTdP9JEHt82dqs6wQ== +"@helium/voter-stake-registry-sdk@^0.2.14": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.14.tgz#650fe667bf9cec21af66cd8123185a7526fed666" + integrity sha512-1/YEPkhenaPhSJNTvU792C1j8QQjVH4fvefPg2NzQo9db5LrlYQpTlH+06HONRd5i4bBxKx5y9dL1LPgyDtBrA== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/anchor-resolvers" "^0.2.5" @@ -3214,6 +3246,20 @@ bn.js "^5.2.0" js-sha3 "^0.8.0" +"@metaplex-foundation/mpl-bubblegum@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-bubblegum/-/mpl-bubblegum-0.7.0.tgz#b34067ad4fe846ceb60e47e49f221ecf4730add7" + integrity sha512-HCo6q+nh8M3KRv9/aUaZcJo5/vPJEeZwPGRDWkqN7lUXoMIvhd83fZi7MB1rIg1gwpVHfHqim0A02LCYKisWFg== + dependencies: + "@metaplex-foundation/beet" "0.7.1" + "@metaplex-foundation/beet-solana" "0.4.0" + "@metaplex-foundation/cusper" "^0.0.2" + "@metaplex-foundation/mpl-token-metadata" "^2.5.2" + "@solana/spl-account-compression" "^0.1.4" + "@solana/spl-token" "^0.1.8" + "@solana/web3.js" "^1.50.1" + js-sha3 "^0.8.0" + "@metaplex-foundation/mpl-candy-guard@^0.3.0": version "0.3.2" resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-candy-guard/-/mpl-candy-guard-0.3.2.tgz#426e89793676b42e9bbb5e523303fba36ccd5281" @@ -3819,6 +3865,15 @@ "@solana/buffer-layout-utils" "^0.2.0" buffer "^6.0.3" +"@solana/spl-token@^0.3.8": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf" + integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + buffer "^6.0.3" + "@solana/wallet-adapter-base@^0.9.2": version "0.9.22" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.22.tgz#97812eaf6aebe01e5fe714326b3c9a0614ae6112" From 3c52c8213fa772db5150c0341477c841ed8c1cea Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Mon, 31 Jul 2023 12:06:42 -0500 Subject: [PATCH 03/23] WIP --- package.json | 11 +- src/App.tsx | 2 +- src/components/TokenIcon.tsx | 1 + src/features/account/AccountTokenScreen.tsx | 35 ++-- src/features/account/TokenListItem.tsx | 11 +- src/features/payment/PaymentCard.tsx | 195 +++++++++----------- src/features/payment/PaymentError.tsx | 32 +++- src/features/payment/PaymentItem.tsx | 7 +- src/features/payment/PaymentScreen.tsx | 150 ++++++--------- src/features/payment/PaymentSubmit.tsx | 22 ++- src/features/payment/PaymentSuccess.tsx | 14 +- src/features/payment/PaymentSummary.tsx | 28 ++- src/hooks/useMetaplexMetadata.ts | 18 +- src/hooks/useSubmitTxn.ts | 27 ++- src/utils/StoreSolBalance.ts | 17 +- src/utils/solanaUtils.ts | 2 +- yarn.lock | 101 +++++++++- 17 files changed, 370 insertions(+), 303 deletions(-) diff --git a/package.json b/package.json index 9d89cb3bd..4ac7f9ff4 100644 --- a/package.json +++ b/package.json @@ -46,18 +46,18 @@ "@helium/currency": "4.11.1", "@helium/currency-utils": "0.1.1", "@helium/data-credits-sdk": "0.2.14", - "@helium/distributor-oracle": "^0.2.14", + "@helium/distributor-oracle": "file:.yalc/@helium/distributor-oracle", "@helium/fanout-sdk": "0.1.2", - "@helium/helium-entity-manager-sdk": "^0.2.14", - "@helium/helium-react-hooks": "0.2.14", + "@helium/helium-entity-manager-sdk": "file:.yalc/@helium/helium-entity-manager-sdk", + "@helium/helium-react-hooks": "file:.yalc/@helium/helium-react-hooks", "@helium/helium-sub-daos-sdk": "0.2.14", "@helium/http": "4.7.5", "@helium/idls": "^0.2.5", - "@helium/lazy-distributor-sdk": "0.1.2", + "@helium/lazy-distributor-sdk": "file:.yalc/@helium/lazy-distributor-sdk", "@helium/onboarding": "4.9.0", "@helium/proto-ble": "4.0.0", "@helium/react-native-sdk": "1.0.0", - "@helium/spl-utils": "^0.2.6", + "@helium/spl-utils": "file:.yalc/@helium/spl-utils", "@helium/transactions": "4.8.1", "@helium/treasury-management-sdk": "0.1.2", "@helium/voter-stake-registry-sdk": "0.1.2", @@ -84,6 +84,7 @@ "@shopify/restyle": "1.8.0", "@solana/spl-account-compression": "0.1.4", "@solana/spl-token": "0.3.6", + "@solana/wallet-adapter-react": "^0.15.33", "@solana/wallet-standard-features": "1.0.0", "@solana/web3.js": "1.64.0", "@tradle/react-native-http": "2.0.1", diff --git a/src/App.tsx b/src/App.tsx index 785e415a2..ee23e4e90 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' import { PortalHost, PortalProvider } from '@gorhom/portal' -import { AccountProvider } from '@helium/helium-react-hooks' +import { AccountProvider } from '@helium/account-fetch-cache-hooks' import { DarkTheme, NavigationContainer } from '@react-navigation/native' import MapboxGL from '@rnmapbox/maps' import { ThemeProvider } from '@shopify/restyle' diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx index 50b5abfaa..3a8b45e20 100644 --- a/src/components/TokenIcon.tsx +++ b/src/components/TokenIcon.tsx @@ -35,6 +35,7 @@ const TokenIcon = ({ ticker, size = 40, white, img }: Props) => { switch (ticker) { default: + return null case 'HNT': return case 'MOBILE': diff --git a/src/features/account/AccountTokenScreen.tsx b/src/features/account/AccountTokenScreen.tsx index 20623c24c..909049c4b 100644 --- a/src/features/account/AccountTokenScreen.tsx +++ b/src/features/account/AccountTokenScreen.tsx @@ -13,11 +13,12 @@ import BottomSheet, { BottomSheetFlatList, WINDOW_HEIGHT, } from '@gorhom/bottom-sheet' -import { Ticker } from '@helium/currency' +import { HNT_MINT, DC_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' import useLayoutHeight from '@hooks/useLayoutHeight' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' import { RouteProp, useRoute } from '@react-navigation/native' +import { NATIVE_MINT } from '@solana/spl-token' import globalStyles from '@theme/globalStyles' import { useColors } from '@theme/themeHooks' import React, { useCallback, useMemo, useRef, useState } from 'react' @@ -74,9 +75,7 @@ const AccountTokenScreen = () => { const mintStr = useMemo(() => route.params.mint, [route.params.mint]) const mint = usePublicKey(mintStr) - const { symbol } = useMetaplexMetadata(mint) - - const routeTicker = useMemo(() => symbol?.toUpperCase() as Ticker, [symbol]) + const { json, symbol } = useMetaplexMetadata(mint) const toggleFiltersOpen = useCallback( (open) => () => { @@ -184,14 +183,14 @@ const AccountTokenScreen = () => { const hasAirdrop = useMemo(() => { if (cluster === 'devnet') { return ( - routeTicker === 'SOL' || - routeTicker === 'HNT' || - routeTicker === 'IOT' || - routeTicker === 'MOBILE' + mint?.equals(NATIVE_MINT) || + mint?.equals(HNT_MINT) || + mint?.equals(IOT_MINT) || + mint?.equals(MOBILE_MINT) ) } return false - }, [routeTicker, cluster]) + }, [mint, cluster]) const renderHeader = useCallback(() => { const filterName = t(`accountsScreen.filterTypes.${filterState.filter}`) @@ -311,7 +310,7 @@ const AccountTokenScreen = () => { const filters = useCallback( () => ( <> - {routeTicker !== 'DC' && ( + {!mint?.equals(DC_MINT) && ( <> { /> )} - {routeTicker === 'DC' && ( + {mint?.equals(DC_MINT) && ( <> { )} ), - [filterState.filter, routeTicker, setFilter, t], + [filterState.filter, mint, setFilter, t], ) const backgroundComponent = useCallback( @@ -393,7 +392,7 @@ const AccountTokenScreen = () => { hasBottomTitle: true, } - if (routeTicker === 'DC') { + if (mint?.equals(DC_MINT)) { options = { hasSend: false, hasRequest: false, @@ -404,14 +403,14 @@ const AccountTokenScreen = () => { } return options - }, [routeTicker]) + }, [mint]) return ( @@ -457,7 +456,7 @@ const AccountTokenScreen = () => { - + { hasRequest={actionBarProps.hasRequest} hasDelegate={actionBarProps.hasDelegate} ticker={routeTicker} - compact={routeTicker !== 'DC'} - hasBottomTitle={routeTicker !== 'DC'} + compact={!mint.equals(DC_MINT)} + hasBottomTitle={!mint.equals(DC_MINT)} hasAirdrop={hasAirdrop} /> diff --git a/src/features/account/TokenListItem.tsx b/src/features/account/TokenListItem.tsx index 25dc5a6c9..58c4b6f0b 100644 --- a/src/features/account/TokenListItem.tsx +++ b/src/features/account/TokenListItem.tsx @@ -27,18 +27,23 @@ const TokenListItem = ({ mint }: Props) => { const { currentAccount } = useAccountStorage() const wallet = usePublicKey(currentAccount?.solanaAddress) const { amount, decimals } = useOwnedAmount(wallet, mint) + // const amount = BigInt(0) + // const decimals = 0 const { triggerImpact } = useHaptic() const { json, symbol } = useMetaplexMetadata(mint) + const mintStr = mint.toBase58() const handleNavigation = useCallback(() => { triggerImpact('light') navigation.navigate('AccountTokenScreen', { - mint: mint.toBase58(), + mint: mintStr, }) - }, [navigation, mint, triggerImpact]) + }, [navigation, mintStr, triggerImpact]) const balanceToDisplay = useMemo(() => { - return amount ? humanReadable(new BN(amount.toString()), decimals) : '' + return amount && typeof decimals !== 'undefined' + ? humanReadable(new BN(amount.toString()), decimals) + : '' }, [amount, decimals]) return ( diff --git a/src/features/payment/PaymentCard.tsx b/src/features/payment/PaymentCard.tsx index f2f5f0b69..480b06d50 100644 --- a/src/features/payment/PaymentCard.tsx +++ b/src/features/payment/PaymentCard.tsx @@ -1,33 +1,30 @@ -import Balance, { - NetworkTokens, - TestNetworkTokens, - Ticker, -} from '@helium/currency' -import { PaymentV2 } from '@helium/transactions' -import React, { memo, useCallback, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { LayoutChangeEvent } from 'react-native' import Box from '@components/Box' -import LedgerPayment, { LedgerPaymentRef } from '@components/LedgerPayment' +import { LedgerPaymentRef } from '@components/LedgerPayment' import SubmitButton from '@components/SubmitButton' import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' -import useAlert from '@hooks/useAlert' +import { PaymentV2 } from '@helium/transactions' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' +import React, { memo, useCallback, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { LayoutChangeEvent } from 'react-native' import { useAccountStorage } from '../../storage/AccountStorageProvider' -import animateTransition from '../../utils/animateTransition' -import PaymentSummary from './PaymentSummary' import { checkSecureAccount } from '../../storage/secureStorage' +import animateTransition from '../../utils/animateTransition' import { SendDetails } from '../../utils/linking' +import PaymentSummary from './PaymentSummary' type Props = { handleCancel: () => void - totalBalance: Balance - feeTokenBalance?: Balance + totalBalance: BN + feeTokenBalance?: BN onSubmit: (opts?: { txn: PaymentV2; txnJson: string }) => void disabled?: boolean errors?: string[] payments?: SendDetails[] - ticker: Ticker + mint: PublicKey } const PaymentCard = ({ @@ -36,20 +33,16 @@ const PaymentCard = ({ feeTokenBalance, onSubmit, disabled, - ticker, + mint, payments, errors, }: Props) => { + const { symbol } = useMetaplexMetadata(mint) const { t } = useTranslation() const [payEnabled, setPayEnabled] = useState(false) const [height, setHeight] = useState(0) const ledgerPaymentRef = useRef(null) - const { showOKAlert } = useAlert() const { currentAccount } = useAccountStorage() - const [options, setOptions] = useState<{ - txn: PaymentV2 - txnJson: string - }>() const handlePayPressed = useCallback(async () => { if (!currentAccount?.ledgerDevice) { @@ -79,102 +72,80 @@ const PaymentCard = ({ }, [height], ) - const handleConfirm = useCallback( - (opts: { txn: PaymentV2; txnJson: string }) => { - setPayEnabled(true) - setOptions(opts) - }, - [], - ) - const handleSubmit = useCallback(() => { - onSubmit(options) - }, [onSubmit, options]) - - const handleLedgerError = useCallback( - (error: Error) => { - showOKAlert({ title: t('generic.error'), message: error.toString() }) - }, - [showOKAlert, t], - ) + const handleSubmit = onSubmit return ( - - - - - {!payEnabled ? ( - <> - - - - {t('generic.cancel')} - - - + + {!payEnabled ? ( + <> + + + + {t('generic.cancel')} + + + + - - {t('payment.pay')} - - - - - ) : ( - - )} - + {t('payment.pay')} + + + + + ) : ( + + )} - + ) } diff --git a/src/features/payment/PaymentError.tsx b/src/features/payment/PaymentError.tsx index 982585c03..94e5f1efa 100644 --- a/src/features/payment/PaymentError.tsx +++ b/src/features/payment/PaymentError.tsx @@ -1,21 +1,26 @@ -import Balance, { NetworkTokens, TestNetworkTokens } from '@helium/currency' -import { useNavigation } from '@react-navigation/native' -import React, { memo, useMemo } from 'react' -import { useTranslation } from 'react-i18next' import FailureIcon from '@assets/images/paymentFailure.svg' -import { SerializedError } from '@reduxjs/toolkit' import BackgroundFill from '@components/BackgroundFill' import Box from '@components/Box' import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' +import { useOwnedAmount } from '@helium/helium-react-hooks' +import { usePublicKey } from '@hooks/usePublicKey' +import { useNavigation } from '@react-navigation/native' +import { SerializedError } from '@reduxjs/toolkit' +import { NATIVE_MINT } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' import { parseTransactionError } from '@utils/solanaUtils' -import { useBalance } from '@utils/Balance' +import BN from 'bn.js' +import React, { memo, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { Payment } from './PaymentItem' import PaymentSummary from './PaymentSummary' type Props = { - totalBalance: Balance - feeTokenBalance?: Balance + mint: PublicKey + totalBalance: BN + feeTokenBalance?: BN payments: Payment[] error?: Error | SerializedError onRetry: () => void @@ -27,15 +32,21 @@ const PaymentError = ({ payments, error, onRetry, + mint, }: Props) => { const navigation = useNavigation() const { t } = useTranslation() - const { solBalance } = useBalance() + const { currentAccount } = useAccountStorage() + const wallet = usePublicKey(currentAccount?.solanaAddress) + const { amount: solBalance } = useOwnedAmount(wallet, NATIVE_MINT) const errorMessage = useMemo(() => { if (!error) return '' - return parseTransactionError(solBalance, error.message) + return parseTransactionError( + new BN(solBalance?.toString() || '0'), + error.message, + ) }, [error, solBalance]) return ( @@ -65,6 +76,7 @@ const PaymentError = ({ > { + const decimals = useMint(mint)?.info?.decimals const { colorStyle } = useOpacity('primaryText', 0.3) const { dcToNetworkTokens, oraclePrice } = useBalance() const { t } = useTranslation() @@ -263,7 +264,9 @@ const PaymentItem = ({ variant="subtitle2" color="primaryText" > - {humanReadable(amount, decimals)} + {typeof amount !== 'undefined' && + typeof decimals !== 'undefined' && + humanReadable(amount, decimals)} {fee && ( diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 54e39d90b..b82d132fa 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -1,9 +1,5 @@ import Close from '@assets/images/close.svg' import QR from '@assets/images/qr.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' -import TokenSOL from '@assets/images/tokenSOL.svg' import AccountButton from '@components/AccountButton' import AccountSelector, { AccountSelectorRef, @@ -22,13 +18,16 @@ import TokenSelector, { } from '@components/TokenSelector' import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address, { NetTypes } from '@helium/address' -import { CurrencyType, Ticker } from '@helium/currency' -import { useOwnedAmount } from '@helium/helium-react-hooks' -import { HNT_MINT } from '@helium/spl-utils' +import { Ticker } from '@helium/currency' +import { useMint, useOwnedAmount } from '@helium/helium-react-hooks' +import { HNT_MINT, humanReadable } from '@helium/spl-utils' import useDisappear from '@hooks/useDisappear' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' +import { NATIVE_MINT } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' +import { useVisibleTokens } from '@storage/TokensProvider' import { useColors, useHitSlop } from '@theme/themeHooks' import { Mints } from '@utils/constants' import { calcCreateAssociatedTokenAccountAccountFee } from '@utils/solanaUtils' @@ -56,19 +55,19 @@ import { CSAccount } from '../../storage/cloudStorage' import { RootState } from '../../store/rootReducer' import { solanaSlice } from '../../store/slices/solanaSlice' import { useAppDispatch } from '../../store/store' -import { balanceToString, useBalance } from '../../utils/Balance' +import { useBalance } from '../../utils/Balance' import { accountNetType, formatAccountAlias, solAddressIsValid, } from '../../utils/accountUtils' import { SendDetails } from '../../utils/linking' +import * as logger from '../../utils/logger' import { HomeNavigationProp, HomeStackParamList, PaymentRouteParam, } from '../home/homeTypes' -import * as logger from '../../utils/logger' import PaymentCard from './PaymentCard' import PaymentItem from './PaymentItem' import PaymentSubmit from './PaymentSubmit' @@ -77,7 +76,7 @@ import usePaymentsReducer, { MAX_PAYMENTS } from './usePaymentsReducer' type LinkedPayment = { amount?: string payee: string - defaultTokenType?: Ticker + mint?: string } const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { @@ -89,7 +88,7 @@ const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { { payee: opts.payee, amount: opts.amount, - defaultTokenType: opts.defaultTokenType?.toUpperCase() as Ticker, + mint: Mints[opts.defaultTokenType?.toUpperCase() as Ticker], }, ] } @@ -104,6 +103,7 @@ const PaymentScreen = () => { const tokenSelectorRef = useRef(null) const hntKeyboardRef = useRef(null) const { oraclePrice } = useBalance() + const { visibleTokens } = useVisibleTokens() const [mint, setMint] = useState(HNT_MINT) const { currentAccount, @@ -114,6 +114,7 @@ const PaymentScreen = () => { sortedAccountsForNetType, } = useAccountStorage() const wallet = usePublicKey(currentAccount?.solanaAddress) + const { amount: solBalance } = useOwnedAmount(wallet, NATIVE_MINT) const { amount: balanceBigint } = useOwnedAmount(wallet, mint) const balance = useMemo(() => { if (typeof balanceBigint !== 'undefined') { @@ -126,7 +127,7 @@ const PaymentScreen = () => { const navigation = useNavigation() const rootNav = useNavigation() const { t } = useTranslation() - const { primaryText, blueBright500, white } = useColors() + const { primaryText } = useColors() const hitSlop = useHitSlop('l') useDisappear(() => { @@ -204,8 +205,8 @@ const PaymentScreen = () => { if (!paymentsArr?.length) return - if (paymentsArr[0].defaultTokenType) { - onTickerSelected(paymentsArr[0].defaultTokenType) + if (paymentsArr[0].mint) { + onTokenSelected(new PublicKey(paymentsArr[0].mint)) } if ( @@ -303,53 +304,42 @@ const PaymentScreen = () => { const insufficientFunds = useMemo((): [ value: boolean, - errorTicker: string, + errorMint: PublicKey | undefined, ] => { - if (!hntBalance || !paymentState.totalAmount) { - return [true, ''] + if (paymentState.balance.isZero()) { + return [true, undefined] } - if (paymentState.networkFee?.integerBalance === undefined) - return [false, ''] + if (typeof paymentState.networkFee === 'undefined') + return [false, undefined] try { let hasEnoughSol = false if (solBalance) { - hasEnoughSol = - solBalance.minus(paymentState.networkFee).integerBalance >= 0 - } - let hasEnoughToken = false - if (mint === 'MOBILE' && mobileBalance) { - hasEnoughToken = - mobileBalance.minus(paymentState.totalAmount).integerBalance >= 0 - } else if (mint === 'IOT' && iotBalance) { - hasEnoughToken = - iotBalance.minus(paymentState.totalAmount).integerBalance >= 0 - } else if (mint === 'HNT' && hntBalance) { - hasEnoughToken = - hntBalance.minus(paymentState.totalAmount).integerBalance >= 0 - } else if (mint === 'SOL' && solBalance) { - hasEnoughToken = - solBalance.minus(paymentState.totalAmount).integerBalance >= 0 + hasEnoughSol = new BN(solBalance.toString()) + .sub(paymentState.networkFee) + .gte(new BN(0)) } + const hasEnoughToken = balance + ?.sub(paymentState.totalAmount) + .gte(new BN(0)) - if (!hasEnoughSol) return [true, 'SOL' as Ticker] - if (!hasEnoughToken) return [true, paymentState.totalAmount.type.mint] - return [false, ''] + if (!hasEnoughSol) return [true, NATIVE_MINT] + if (!hasEnoughToken) return [true, mint] + return [false, undefined] } catch (e) { // if the screen was already open, then a deep link of a different net type // is selected there will be a brief arithmetic error that can be ignored. if (__DEV__) { console.warn(e) } - return [false, ''] + return [false, undefined] } }, [ - hntBalance, - paymentState.totalAmount, + paymentState.balance, paymentState.networkFee, + paymentState.totalAmount, solBalance, + balance, mint, - mobileBalance, - iotBalance, ]) const selfPay = useMemo( @@ -398,11 +388,7 @@ const PaymentScreen = () => { ]) const isFormValid = useMemo(() => { - if ( - selfPay || - !paymentState.networkFee?.integerBalance || - (!!currentAccount?.ledgerDevice && paymentState.payments.length > 1) // ledger payments are limited to one payee - ) { + if (selfPay || !paymentState.networkFee) { return false } @@ -411,25 +397,24 @@ const PaymentScreen = () => { paymentState.payments.every((p) => { const addressValid = !!(p.address && solAddressIsValid(p.address)) - const paymentValid = p.amount && p.amount.integerBalance > 0 + const paymentValid = p.amount && p.amount?.gt(new BN(0)) return addressValid && paymentValid && !p.hasError }) return paymentsValid && !insufficientFunds[0] - }, [selfPay, paymentState, currentAccount, insufficientFunds]) + }, [selfPay, paymentState, insufficientFunds]) const handleTokenTypeSelected = useCallback(() => { tokenSelectorRef?.current?.showTokens() }, []) - const onTickerSelected = useCallback( - (tick: Ticker) => { - setMint(tick) - setMint(Mints[tick]) + const onTokenSelected = useCallback( + (m: PublicKey) => { + setMint(m) dispatch({ type: 'changeToken', - mint: CurrencyType.fromTicker(tick), + mint: m, }) }, [dispatch], @@ -607,49 +592,21 @@ const PaymentScreen = () => { accountSelectorRef?.current.showAccountTypes(netType)() }, [sortedAccountsForNetType]) + const decimals = useMint(mint)?.info?.decimals + const { symbol } = useMetaplexMetadata(mint) const tokenButtonBalance = useMemo(() => { - switch (mint) { - case 'HNT': - return balanceToString(hntBalance) - case 'SOL': - return balanceToString(solBalance) - case 'MOBILE': - return balanceToString(mobileBalance) - case 'IOT': - return balanceToString(iotBalance) + if (typeof balance !== 'undefined' && typeof decimals !== 'undefined') { + return humanReadable(balance, decimals) } - }, [mint, hntBalance, solBalance, mobileBalance, iotBalance]) + }, [balance, decimals]) const data = useMemo((): TokenListItem[] => { - const tokens = [ - { - label: 'HNT', - icon: , - value: 'HNT' as Ticker, - selected: mint === 'HNT', - }, - { - label: 'MOBILE', - icon: , - value: 'MOBILE' as Ticker, - selected: mint === 'MOBILE', - }, - { - label: 'IOT', - icon: , - value: 'IOT' as Ticker, - selected: mint === 'IOT', - }, - { - label: 'SOL', - icon: , - value: 'SOL' as Ticker, - selected: mint === 'SOL', - }, - ] - + const tokens = [...visibleTokens].map((token) => ({ + mint: new PublicKey(token), + selected: mint.toBase58() === token, + })) return tokens - }, [blueBright500, white, mint]) + }, [mint, visibleTokens]) return ( <> @@ -667,7 +624,7 @@ const PaymentScreen = () => { > { { onEditAddress={handleEditAddress} handleAddressError={handleAddressError} onUpdateError={handleSetPaymentError} - mint={mint.mint} + mint={mint} onRemove={ paymentState.payments.length > 1 ? handleRemove @@ -808,6 +765,7 @@ const PaymentScreen = () => { - feeTokenBalance?: Balance + totalBalance: BN + feeTokenBalance?: BN payments?: Payment[] onRetry: () => void onSuccess: () => void @@ -23,6 +25,7 @@ type Props = { } const PaymentSubmit = ({ + mint, submitLoading, submitError, submitSucceeded, @@ -87,6 +90,7 @@ const PaymentSubmit = ({ return ( - feeTokenBalance?: Balance + mint: PublicKey + totalBalance: BN + feeTokenBalance?: BN payments: Payment[] onSuccess: () => void actionTitle: string @@ -23,6 +25,7 @@ const PaymentSuccess = ({ payments, onSuccess, actionTitle, + mint, }: Props) => { const { t } = useTranslation() return ( @@ -41,6 +44,7 @@ const PaymentSuccess = ({ > - feeTokenBalance?: Balance + mint: PublicKey + totalBalance: BN + feeTokenBalance?: BN disabled?: boolean errors?: string[] payments?: Payment[] @@ -25,17 +28,22 @@ const PaymentSummary = ({ payments = [], errors, alwaysShowRecipients, + mint, }: Props) => { const { t } = useTranslation() + const decimals = useMint(mint)?.info?.decimals + + const total = useMemo(() => { + if (typeof totalBalance === 'undefined' || typeof decimals === 'undefined') + return '' - const total = useMemo(() => balanceToString(totalBalance), [totalBalance]) + return humanReadable(totalBalance, decimals) + }, [totalBalance, decimals]) const fee = useMemo( () => feeTokenBalance ? t('payment.fee', { - value: balanceToString(feeTokenBalance, { - maxDecimalPlaces: 4, - }), + value: humanReadable(feeTokenBalance, 9), }) : '', [feeTokenBalance, t], diff --git a/src/hooks/useMetaplexMetadata.ts b/src/hooks/useMetaplexMetadata.ts index 80e0f1737..820d43883 100644 --- a/src/hooks/useMetaplexMetadata.ts +++ b/src/hooks/useMetaplexMetadata.ts @@ -1,9 +1,10 @@ import { TypedAccountParser } from '@helium/account-fetch-cache' -import { useAccount } from '@helium/helium-react-hooks' +import { useAccount } from '@helium/account-fetch-cache-hooks' import { Metadata, + parseMetadataAccount, + sol, toMetadata, - toMetadataAccount, } from '@metaplex-foundation/js' import { PublicKey } from '@solana/web3.js' import { useMemo } from 'react' @@ -40,10 +41,15 @@ export function useMetaplexMetadata(mint: PublicKey | undefined): { // eslint-disable-next-line react-hooks/exhaustive-deps }, [mint?.toBase58()]) const parser: TypedAccountParser = useMemo(() => { - return (_, account) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return toMetadata(toMetadataAccount(account)) + return (publicKey, account) => { + return toMetadata( + parseMetadataAccount({ + ...account, + lamports: sol(account.lamports), + data: account.data, + publicKey, + }), + ) } }, []) const { info: metadataAcc, loading } = useAccount(metadataAddr, parser) diff --git a/src/hooks/useSubmitTxn.ts b/src/hooks/useSubmitTxn.ts index a4ba12936..ed2f26984 100644 --- a/src/hooks/useSubmitTxn.ts +++ b/src/hooks/useSubmitTxn.ts @@ -1,21 +1,23 @@ -import { useCallback } from 'react' -import Balance, { AnyCurrencyType } from '@helium/currency' +import { HotspotType } from '@helium/onboarding' +import { chunks } from '@helium/spl-utils' import { PublicKey, Transaction } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' import i18n from '@utils/i18n' -import { Mints } from '@utils/constants' import * as solUtils from '@utils/solanaUtils' -import { useAccountStorage } from '@storage/AccountStorageProvider' -import { HotspotType } from '@helium/onboarding' +import BN from 'bn.js' +import { useCallback } from 'react' +import { useSolana } from '../solana/SolanaProvider' +import { useWalletSign } from '../solana/WalletSignProvider' import { WalletStandardMessageTypes } from '../solana/walletSignBottomSheetTypes' import { + claimAllRewards, + claimRewards, makeCollectablePayment, makePayment, - claimRewards, - claimAllRewards, sendAnchorTxn, - sendTreasurySwap, - sendMintDataCredits, sendDelegateDataCredits, + sendMintDataCredits, + sendTreasurySwap, sendUpdateIotInfo, sendUpdateMobileInfo, } from '../store/slices/solanaSlice' @@ -24,12 +26,7 @@ import { Collectable, CompressedNFT, HotspotWithPendingRewards, - toMintAddress, } from '../types/solana' -import { useSolana } from '../solana/SolanaProvider' -import { useWalletSign } from '../solana/WalletSignProvider' -import BN from 'bn.js' -import { chunks } from '@helium/spl-utils' export default () => { const { currentAccount } = useAccountStorage() @@ -64,7 +61,7 @@ export default () => { currentAccount.solanaAddress!, currentAccount.address, p, - mint, + mint.toBase58(), ) }), ) diff --git a/src/utils/StoreSolBalance.ts b/src/utils/StoreSolBalance.ts index cb5737aaf..a0fe5a8ff 100644 --- a/src/utils/StoreSolBalance.ts +++ b/src/utils/StoreSolBalance.ts @@ -1,34 +1,33 @@ -import { useAccount } from '@helium/helium-react-hooks' -import { PublicKey } from '@solana/web3.js' +import { useSolOwnedAmount } from '@helium/helium-react-hooks' +import { usePublicKey } from '@hooks/usePublicKey' import { useEffect } from 'react' +import { useSolana } from '../solana/SolanaProvider' import { balancesSlice } from '../store/slices/balancesSlice' import { useAppDispatch } from '../store/store' -import { useSolana } from '../solana/SolanaProvider' type Props = { solanaAddress: string } const StoreSolBalance = ({ solanaAddress }: Props) => { - const account = useAccount(new PublicKey(solanaAddress)) + const key = usePublicKey(solanaAddress) + const { amount } = useSolOwnedAmount(key) const dispatch = useAppDispatch() const { cluster } = useSolana() useEffect(() => { - if (account.account?.lamports === undefined) return - - const amount = account.account?.lamports + if (typeof amount === 'undefined') return dispatch( balancesSlice.actions.updateBalance({ cluster, solanaAddress, - balance: amount, + balance: Number(amount), type: 'sol', tokenAccount: solanaAddress, }), ) - }, [account.account?.lamports, cluster, dispatch, solanaAddress]) + }, [amount, cluster, dispatch, solanaAddress]) return null } diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 3a43588a6..314b0c3ae 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -1494,7 +1494,7 @@ export const submitSolana = async ({ } export const parseTransactionError = (balance?: BN, message?: string) => { - if (balance?.gt(new BN(0.02))) { + if (balance?.lt(new BN(0.02))) { return 'The SOL balance on this account is too low to complete this transaction' } diff --git a/yarn.lock b/yarn.lock index 65e36f3cd..8ae1a13ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3454,6 +3454,13 @@ dependencies: merge-options "^3.0.4" +"@react-native-async-storage/async-storage@^1.17.7": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.19.1.tgz#09d35caaa31823b40fdfeebf95decf8f992a6274" + integrity sha512-5QXuGCtB+HL3VtKL2JN3+6t4qh8VXizK+aGDAv6Dqiq3MLrzgZHb4tjVgtEWMd8CcDtD/JqaAI1b6/EaYGtFIA== + dependencies: + merge-options "^3.0.4" + "@react-native-community/blur@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@react-native-community/blur/-/blur-4.3.0.tgz#e5018b3b0bd6de9632ac6cf34e9f8e0f1a9a28ec" @@ -3796,6 +3803,30 @@ dependencies: "@sinonjs/commons" "^2.0.0" +"@solana-mobile/mobile-wallet-adapter-protocol-web3js@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@solana-mobile/mobile-wallet-adapter-protocol-web3js/-/mobile-wallet-adapter-protocol-web3js-2.0.1.tgz#096599cd2073d35f77be9121222e654321b16e85" + integrity sha512-zpMEU40PJ2rmRgqXbn7JqyHhrygQLX9MVszAczVDzqg39aMD7yo6VyTI8NEH422JzCj3Cjl9DndQZJRNdqdOHw== + dependencies: + "@solana-mobile/mobile-wallet-adapter-protocol" "^1.0.0" + bs58 "^5.0.0" + js-base64 "^3.7.2" + +"@solana-mobile/mobile-wallet-adapter-protocol@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@solana-mobile/mobile-wallet-adapter-protocol/-/mobile-wallet-adapter-protocol-1.0.1.tgz#31bf53610c6502a40a1545b9d73009bf3f32d0d6" + integrity sha512-T+xroGLYaYeI8TXy85oNul2m1k/oF9dAW7eRy/MF9up1EQ5SPL5KWFICQfV2gy87jBZd1y0k0M2GayVN7QdQpA== + +"@solana-mobile/wallet-adapter-mobile@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@solana-mobile/wallet-adapter-mobile/-/wallet-adapter-mobile-2.0.1.tgz#1f36b56013c5bd016a4c846b1c00b31452083139" + integrity sha512-QIM8nqVRmv8yEsTEfMbzWH7NoVVC6F417Z/tf6FpOVn1N6MqiZc5kvajsaRJKXrHzi5r5023SKErjg9LjZshXw== + dependencies: + "@react-native-async-storage/async-storage" "^1.17.7" + "@solana-mobile/mobile-wallet-adapter-protocol-web3js" "^2.0.1" + "@solana/wallet-adapter-base" "^0.9.17" + js-base64 "^3.7.2" + "@solana/buffer-layout-utils@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" @@ -3874,7 +3905,7 @@ "@solana/buffer-layout-utils" "^0.2.0" buffer "^6.0.3" -"@solana/wallet-adapter-base@^0.9.2": +"@solana/wallet-adapter-base@^0.9.17", "@solana/wallet-adapter-base@^0.9.2", "@solana/wallet-adapter-base@^0.9.21", "@solana/wallet-adapter-base@^0.9.22": version "0.9.22" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.22.tgz#97812eaf6aebe01e5fe714326b3c9a0614ae6112" integrity sha512-xbLEZPGSJFvgTeldG9D55evhl7QK/3e/F7vhvcA97mEt1eieTgeKMnGlmmjs3yivI3/gtZNZeSk1XZLnhKcQvw== @@ -3884,6 +3915,22 @@ "@wallet-standard/features" "^1.0.3" eventemitter3 "^4.0.7" +"@solana/wallet-adapter-react@^0.15.33": + version "0.15.33" + resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-react/-/wallet-adapter-react-0.15.33.tgz#85c7e6528cb572338cc5fa331e2371a3c2818392" + integrity sha512-0bkP6wbWuLtIiswwFke4XjF++Tbyk+cBa1XY7r4msMCg+oQo+BA3TvoiuuL3dPOgsZ2f21A+cr6ekXZ5nfZNIA== + dependencies: + "@solana-mobile/wallet-adapter-mobile" "^2.0.0" + "@solana/wallet-adapter-base" "^0.9.22" + "@solana/wallet-standard-wallet-adapter-react" "^1.0.2" + +"@solana/wallet-standard-chains@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@solana/wallet-standard-chains/-/wallet-standard-chains-1.0.0.tgz#4c291a2f79f0e105ce0a47b30d98dbd7f8ba69be" + integrity sha512-kr3+JAo7mEBhVCH9cYzjn/vXeUiZeYfB4BF6E8u3U2jq3KlZA/KB+YM976+zGumTfN0NmMXUm066pTTG9kJsNQ== + dependencies: + "@wallet-standard/base" "^1.0.0" + "@solana/wallet-standard-features@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@solana/wallet-standard-features/-/wallet-standard-features-1.0.0.tgz#fee71c47fa8c4bacbdc5c8750487e60a2e5e6746" @@ -3892,7 +3939,7 @@ "@wallet-standard/base" "^1.0.0" "@wallet-standard/features" "^1.0.0" -"@solana/wallet-standard-features@^1.0.1": +"@solana/wallet-standard-features@^1.0.0", "@solana/wallet-standard-features@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@solana/wallet-standard-features/-/wallet-standard-features-1.0.1.tgz#36270a646f74a80e51b9e21fb360edb64f840c68" integrity sha512-SUfx7KtBJ55XIj0qAhhVcC1I6MklAXqWFEz9hDHW+6YcJIyvfix/EilBhaBik1FJ2JT0zukpOfFv8zpuAbFRbw== @@ -3900,6 +3947,37 @@ "@wallet-standard/base" "^1.0.1" "@wallet-standard/features" "^1.0.3" +"@solana/wallet-standard-util@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@solana/wallet-standard-util/-/wallet-standard-util-1.0.0.tgz#597b3a240f1855d25d93c2cd7efe8631a1a241ac" + integrity sha512-qRAOBXnN7dwvtgzTtxIHsSeJAMbGNZdSWs57TT8pCyBrKL5dVxaK2u95Dm17SRSzwfKl/EByV1lTjOxXyKWS+g== + dependencies: + "@solana/wallet-standard-chains" "^1.0.0" + "@solana/wallet-standard-features" "^1.0.0" + +"@solana/wallet-standard-wallet-adapter-base@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@solana/wallet-standard-wallet-adapter-base/-/wallet-standard-wallet-adapter-base-1.0.2.tgz#d6028c27381fe384a34a7426b3eb828a1da6bff0" + integrity sha512-QqkupdWvWuihX87W6ijt174u6ZdP5OSFlNhZhuhoMlIdyI/sj7MhGsdppuRlMh65oVO2WNWTL9y2bO5Pbx+dfg== + dependencies: + "@solana/wallet-adapter-base" "^0.9.21" + "@solana/wallet-standard-chains" "^1.0.0" + "@solana/wallet-standard-features" "^1.0.1" + "@solana/wallet-standard-util" "^1.0.0" + "@wallet-standard/app" "^1.0.1" + "@wallet-standard/base" "^1.0.1" + "@wallet-standard/features" "^1.0.3" + "@wallet-standard/wallet" "^1.0.1" + +"@solana/wallet-standard-wallet-adapter-react@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@solana/wallet-standard-wallet-adapter-react/-/wallet-standard-wallet-adapter-react-1.0.2.tgz#8359900de37b05f921eb2318162a4dc179ce9864" + integrity sha512-0YTPUnjiSG5ajDP2hK8EipxkeHhO3+nCtXeF1eS/ZP2QcFAgS/4luywrn/6CdfzQ2cQYPCFdnG/QculpUp6bBg== + dependencies: + "@solana/wallet-standard-wallet-adapter-base" "^1.0.2" + "@wallet-standard/app" "^1.0.1" + "@wallet-standard/base" "^1.0.1" + "@solana/web3.js@1.64.0": version "1.64.0" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.64.0.tgz#b7f5a976976039a0161242e94d6e1224ab5d30f9" @@ -4856,6 +4934,13 @@ "@urql/core" ">=2.3.1" wonka "^4.0.14" +"@wallet-standard/app@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wallet-standard/app/-/app-1.0.1.tgz#f83c3ae887f7fb52497a7b259bba734ae10a2994" + integrity sha512-LnLYq2Vy2guTZ8GQKKSXQK3+FRGPil75XEdkZqE6fiLixJhZJoJa5hT7lXxwe0ykVTt9LEThdTbOpT7KadS26Q== + dependencies: + "@wallet-standard/base" "^1.0.1" + "@wallet-standard/base@^1.0.0", "@wallet-standard/base@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@wallet-standard/base/-/base-1.0.1.tgz#860dd94d47c9e3c5c43b79d91c6afdbd7a36264e" @@ -4868,6 +4953,13 @@ dependencies: "@wallet-standard/base" "^1.0.1" +"@wallet-standard/wallet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wallet-standard/wallet/-/wallet-1.0.1.tgz#95438941a2a1ee12a794444357b59d53e19b374c" + integrity sha512-qkhJeuQU2afQTZ02yMZE5SFc91Fo3hyFjFkpQglHudENNyiSG0oUKcIjky8X32xVSaumgTZSQUAzpXnCTWHzKQ== + dependencies: + "@wallet-standard/base" "^1.0.1" + "@walletconnect/core@2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.2.1.tgz#2dc6b8b2c438919a800ae4b76832661b922a3511" @@ -10522,6 +10614,11 @@ join-component@^1.1.0: resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5" integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ== +js-base64@^3.7.2: + version "3.7.5" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca" + integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA== + js-sha256@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" From 714ba54d50918436331106998b8af31ecfe1010b Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Mon, 31 Jul 2023 18:59:15 -0500 Subject: [PATCH 04/23] Payments working --- src/components/HNTKeyboard.tsx | 35 +- src/features/account/AccountActionBar.tsx | 24 +- .../account/AccountManageTokenListScreen.tsx | 7 +- src/features/account/AccountTokenBalance.tsx | 136 ++++---- src/features/account/AccountTokenList.tsx | 13 +- src/features/account/AccountTokenScreen.tsx | 56 ++-- src/features/account/AccountsScreen.tsx | 67 ++-- src/features/account/AirdropScreen.tsx | 47 +-- src/features/account/TokenListItem.tsx | 68 ++-- src/features/burn/BurnScreen.tsx | 16 +- src/features/collectables/HotspotList.tsx | 2 +- src/features/home/homeTypes.ts | 3 +- src/features/payment/PaymentItem.tsx | 32 +- src/features/payment/PaymentScreen.tsx | 34 +- src/features/payment/PaymentSummary.tsx | 7 +- src/features/payment/usePaymentsReducer.ts | 15 +- src/features/request/RequestScreen.tsx | 5 +- src/hooks/useSimulatedTransaction.ts | 4 +- src/locales/en.ts | 2 +- src/solana/WalletSignBottomSheet.tsx | 68 ++-- .../WalletSignBottomSheetTransaction.tsx | 2 +- src/storage/TokensProvider.tsx | 4 +- src/store/slices/balancesSlice.ts | 44 +-- src/store/store.ts | 1 + src/types/balance.ts | 2 - src/utils/Balance.tsx | 28 +- src/utils/StoreSolBalance.ts | 1 - src/utils/StoreTokenBalance.ts | 9 +- src/utils/solanaUtils.ts | 52 ++- yarn.lock | 315 +++++++++++++----- 30 files changed, 648 insertions(+), 451 deletions(-) diff --git a/src/components/HNTKeyboard.tsx b/src/components/HNTKeyboard.tsx index 94100a6a1..5db8f70b4 100644 --- a/src/components/HNTKeyboard.tsx +++ b/src/components/HNTKeyboard.tsx @@ -6,7 +6,6 @@ import { } from '@gorhom/bottom-sheet' import { Portal } from '@gorhom/portal' import { useMint, useOwnedAmount } from '@helium/helium-react-hooks' -import { humanReadable } from '@helium/spl-utils' import useBackHandler from '@hooks/useBackHandler' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' @@ -34,6 +33,7 @@ import { Edge } from 'react-native-safe-area-context' import { Payment } from '../features/payment/PaymentItem' import { CSAccount } from '../storage/cloudStorage' import { decimalSeparator, groupSeparator } from '../utils/i18n' +import { humanReadable } from '../utils/solanaUtils' import AccountIcon from './AccountIcon' import BackgroundFill from './BackgroundFill' import Box from './Box' @@ -87,7 +87,7 @@ const HNTKeyboardSelector = forwardRef( const decimals = useMint(mint)?.info?.decimals const { t } = useTranslation() const bottomSheetModalRef = useRef(null) - const { symbol } = useMetaplexMetadata(mint) + const { symbol, loading: loadingMeta } = useMetaplexMetadata(mint) const { backgroundStyle } = useOpacity('surfaceSecondary', 1) const [value, setValue] = useState('0') const [originalValue, setOriginalValue] = useState('') @@ -119,12 +119,13 @@ const HNTKeyboardSelector = forwardRef( }, [payee]) const valueAsBalance = useMemo(() => { - const stripped = value - .replaceAll(groupSeparator, '') - .replaceAll(decimalSeparator, '.') + if (!value || typeof decimals === 'undefined') return undefined + const [whole, dec] = value.split(decimalSeparator) + const decPart = (dec || '').padEnd(decimals, '0').slice(0, decimals) + const fullStr = `${whole.replaceAll(groupSeparator, '')}${decPart}` - return new BN(stripped) - }, [value]) + return new BN(fullStr) + }, [value, decimals]) const hasMaxDecimals = useMemo(() => { if (!valueAsBalance || typeof decimals === 'undefined') return false @@ -202,8 +203,7 @@ const HNTKeyboardSelector = forwardRef( maxBalance = networkFee ? maxBalance?.sub(networkFee) : maxBalance } - const val = - maxBalance && decimals ? humanReadable(maxBalance, decimals) : '0' + const val = humanReadable(maxBalance, decimals) || '0' setValue(maxEnabled ? '0' : val) setMaxEnabled((m) => !m) @@ -248,11 +248,13 @@ const HNTKeyboardSelector = forwardRef( > - - {t('hntKeyboard.enterAmount', { - ticker: symbol, - })} - + {!loadingMeta && ( + + {t('hntKeyboard.enterAmount', { + ticker: symbol, + })} + + )} { diff --git a/src/features/account/AccountActionBar.tsx b/src/features/account/AccountActionBar.tsx index 14f1cdae0..032d404f0 100644 --- a/src/features/account/AccountActionBar.tsx +++ b/src/features/account/AccountActionBar.tsx @@ -1,14 +1,14 @@ -import React, { useCallback, useMemo } from 'react' -import { useNavigation } from '@react-navigation/native' -import { useTranslation } from 'react-i18next' -import { LayoutChangeEvent } from 'react-native' -import { Ticker } from '@helium/currency' import Box from '@components/Box' import FabButton from '@components/FabButton' import Text from '@components/Text' +import { useNavigation } from '@react-navigation/native' +import { PublicKey } from '@solana/web3.js' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { LayoutChangeEvent } from 'react-native' +import { useAccountStorage } from '../../storage/AccountStorageProvider' import { useAppStorage } from '../../storage/AppStorageProvider' import { HomeNavigationProp } from '../home/homeTypes' -import { useAccountStorage } from '../../storage/AccountStorageProvider' export type Action = | 'send' @@ -21,7 +21,7 @@ export type Action = | 'airdrop' type Props = { - ticker?: Ticker + mint?: PublicKey onLayout?: (event: LayoutChangeEvent) => void compact?: boolean maxCompact?: boolean @@ -43,7 +43,7 @@ const AccountActionBar = ({ hasDelegate, hasSwaps, hasAirdrop, - ticker, + mint, }: Props) => { const navigation = useNavigation() const { t } = useTranslation() @@ -58,7 +58,7 @@ const AccountActionBar = ({ navigation.navigate('ConfirmPin', { action: 'payment' }) } else { navigation.navigate('PaymentScreen', { - defaultTokenType: ticker, + mint: mint?.toBase58(), }) } break @@ -72,7 +72,9 @@ const AccountActionBar = ({ break } case 'airdrop': { - navigation.navigate('AirdropScreen', { ticker: ticker || 'HNT' }) + if (mint) { + navigation.navigate('AirdropScreen', { mint: mint?.toBase58() }) + } break } case '5G': { @@ -93,7 +95,7 @@ const AccountActionBar = ({ } } }, - [navigation, pin, requirePinForPayment, ticker], + [pin?.status, requirePinForPayment, navigation, mint], ) const fabMargin = useMemo(() => { diff --git a/src/features/account/AccountManageTokenListScreen.tsx b/src/features/account/AccountManageTokenListScreen.tsx index 4f6d74f1d..dd8ea8ccb 100644 --- a/src/features/account/AccountManageTokenListScreen.tsx +++ b/src/features/account/AccountManageTokenListScreen.tsx @@ -7,7 +7,7 @@ import TokenIcon from '@components/TokenIcon' import TouchableContainer from '@components/TouchableContainer' import { Ticker } from '@helium/currency' import { useOwnedAmount } from '@helium/helium-react-hooks' -import { DC_MINT, humanReadable } from '@helium/spl-utils' +import { DC_MINT } from '@helium/spl-utils' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' import CheckBox from '@react-native-community/checkbox' @@ -17,6 +17,7 @@ import { useAccountStorage } from '@storage/AccountStorageProvider' import { useVisibleTokens } from '@storage/TokensProvider' import { useColors, useHitSlop } from '@theme/themeHooks' import { useBalance } from '@utils/Balance' +import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { memo, useCallback, useMemo } from 'react' import { FlatList } from 'react-native-gesture-handler' @@ -42,7 +43,9 @@ const CheckableTokenListItem = ({ const { amount, decimals } = useOwnedAmount(wallet, mint) const { json, symbol } = useMetaplexMetadata(mint) const balanceToDisplay = useMemo(() => { - return amount ? humanReadable(new BN(amount.toString()), decimals) : '' + return amount && typeof decimals !== 'undefined' + ? humanReadable(new BN(amount.toString()), decimals) + : '' }, [amount, decimals]) const colors = useColors() diff --git a/src/features/account/AccountTokenBalance.tsx b/src/features/account/AccountTokenBalance.tsx index 8533c53a9..6835a86ee 100644 --- a/src/features/account/AccountTokenBalance.tsx +++ b/src/features/account/AccountTokenBalance.tsx @@ -1,81 +1,98 @@ -import { Ticker } from '@helium/currency' -import { BoxProps } from '@shopify/restyle' -import React, { memo, useMemo } from 'react' +import Box from '@components/Box' import Text from '@components/Text' import TextTransform from '@components/TextTransform' -import Box from '@components/Box' +import { useOwnedAmount, useTokenAccount } from '@helium/helium-react-hooks' +import { DC_MINT } from '@helium/spl-utils' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import { BoxProps } from '@shopify/restyle' +import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' import { Theme } from '@theme/theme' +import { IOT_SUB_DAO_KEY, MOBILE_SUB_DAO_KEY } from '@utils/constants' +import { getEscrowTokenAccount, humanReadable } from '@utils/solanaUtils' +import BN from 'bn.js' +import React, { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useBalance } from '../../utils/Balance' type Props = { - ticker: Ticker + mint: PublicKey textVariant?: 'h0' | 'h1' | 'h2' | 'h2Medium' showTicker?: boolean } & BoxProps +const EscrowDetails = () => { + const { t } = useTranslation() + const { currentAccount } = useAccountStorage() + + const iotEscrow = getEscrowTokenAccount( + currentAccount?.solanaAddress, + IOT_SUB_DAO_KEY, + ) + const mobileEscrow = getEscrowTokenAccount( + currentAccount?.solanaAddress, + MOBILE_SUB_DAO_KEY, + ) + const { info: iotEscrowAcct } = useTokenAccount(iotEscrow) + const { info: mobileEscrowAcct } = useTokenAccount(mobileEscrow) + + return ( + + + {t('accountsScreen.receivedBalance', { + amount: humanReadable( + new BN(iotEscrowAcct?.amount?.toString() || '0').add( + new BN(mobileEscrowAcct?.amount?.toString() || '0'), + ), + 6, + ), + })} + + + ) +} + const AccountTokenBalance = ({ - ticker, + mint, textVariant, showTicker = true, ...boxProps }: Props) => { + const { currentAccount } = useAccountStorage() + const wallet = usePublicKey(currentAccount?.solanaAddress) const { - dcBalance, - mobileBalance, - iotBalance, - solBalance, - hntBalance, - dcEscrowBalance, - } = useBalance() - const { t } = useTranslation() - - const balance = useMemo(() => { - switch (ticker) { - default: - case 'HNT': { - return hntBalance - } - case 'MOBILE': - return mobileBalance - case 'IOT': - return iotBalance - case 'SOL': - return solBalance - case 'DC': - return dcBalance - } - }, [dcBalance, mobileBalance, hntBalance, solBalance, iotBalance, ticker]) + amount: balance, + decimals, + loading: loadingOwned, + } = useOwnedAmount(wallet, mint) + const balanceStr = + typeof decimals !== 'undefined' && balance + ? humanReadable(new BN(balance?.toString() || '0'), decimals) + : undefined + const { symbol } = useMetaplexMetadata(mint) const tokenDetails = useMemo(() => { - if (ticker !== 'DC' || !showTicker) return + if (!mint.equals(DC_MINT) || !showTicker) return - return ( - - - {t('accountsScreen.receivedBalance', { - amount: dcEscrowBalance?.toString(2, { showTicker: false }), - })} - - - ) - }, [ticker, showTicker, t, dcEscrowBalance]) + return + }, [mint, showTicker]) return ( - {!showTicker && ( - - {typeof balance === 'number' - ? balance - : `${balance?.toString(2, { showTicker: false })}`} - - )} + {!showTicker && + (loadingOwned ? ( + + ) : ( + + {balanceStr} + + ))} {showTicker && ( )} diff --git a/src/features/account/AccountTokenList.tsx b/src/features/account/AccountTokenList.tsx index 9bf72a49f..c8169c06d 100644 --- a/src/features/account/AccountTokenList.tsx +++ b/src/features/account/AccountTokenList.tsx @@ -6,7 +6,7 @@ import { BottomSheetFlatListProps } from '@gorhom/bottom-sheet/lib/typescript/co import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' import { useNavigation } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' -import { useVisibleTokens } from '@storage/TokensProvider' +import { useVisibleTokens, DEFAULT_TOKENS } from '@storage/TokensProvider' import { useBalance } from '@utils/Balance' import { times } from 'lodash' import React, { useCallback, useMemo } from 'react' @@ -40,17 +40,22 @@ const AccountTokenList = ({ onLayout }: Props) => { const { tokenAccounts } = useBalance() const { bottom } = useSafeAreaInsets() const mints = useMemo(() => { - return tokenAccounts + const taMints = tokenAccounts ?.filter( (ta) => visibleTokens.has(ta.mint) && ta.balance > 0 && (ta.decimals > 0 || ta.mint === DC_MINT.toBase58()), ) - .map((ta) => new PublicKey(ta.mint)) + .map((ta) => ta.mint) + + const all = [...new Set([...DEFAULT_TOKENS, ...(taMints || [])])] .sort((a, b) => { - return getSortValue(b.toBase58()) - getSortValue(a.toBase58()) + return getSortValue(b) - getSortValue(a) }) + .map((mint) => new PublicKey(mint)) + + return all }, [tokenAccounts, visibleTokens]) const bottomSpace = useMemo(() => bottom * 2, [bottom]) diff --git a/src/features/account/AccountTokenScreen.tsx b/src/features/account/AccountTokenScreen.tsx index 909049c4b..d3fd7c4f5 100644 --- a/src/features/account/AccountTokenScreen.tsx +++ b/src/features/account/AccountTokenScreen.tsx @@ -13,7 +13,8 @@ import BottomSheet, { BottomSheetFlatList, WINDOW_HEIGHT, } from '@gorhom/bottom-sheet' -import { HNT_MINT, DC_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { Ticker } from '@helium/currency' +import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' import useLayoutHeight from '@hooks/useLayoutHeight' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' @@ -73,7 +74,8 @@ const AccountTokenScreen = () => { ] = useState(true) const mintStr = useMemo(() => route.params.mint, [route.params.mint]) - const mint = usePublicKey(mintStr) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const mint = usePublicKey(mintStr)! const { json, symbol } = useMetaplexMetadata(mint) @@ -183,10 +185,10 @@ const AccountTokenScreen = () => { const hasAirdrop = useMemo(() => { if (cluster === 'devnet') { return ( - mint?.equals(NATIVE_MINT) || - mint?.equals(HNT_MINT) || - mint?.equals(IOT_MINT) || - mint?.equals(MOBILE_MINT) + mint.equals(NATIVE_MINT) || + mint.equals(HNT_MINT) || + mint.equals(IOT_MINT) || + mint.equals(MOBILE_MINT) ) } return false @@ -310,7 +312,7 @@ const AccountTokenScreen = () => { const filters = useCallback( () => ( <> - {!mint?.equals(DC_MINT) && ( + {!mint.equals(DC_MINT) && ( <> { /> )} - {mint?.equals(DC_MINT) && ( + {mint.equals(DC_MINT) && ( <> { hasBottomTitle: true, } - if (mint?.equals(DC_MINT)) { + if (mint.equals(DC_MINT)) { options = { hasSend: false, hasRequest: false, @@ -435,20 +437,22 @@ const AccountTokenScreen = () => { showTicker={false} textVariant="h2Medium" justifyContent="flex-start" - ticker={routeTicker} + mint={mint} flex={1} /> - + {!!symbol && ( + + )} @@ -458,19 +462,21 @@ const AccountTokenScreen = () => { - - + + {!!symbol && ( + + )} { const widgetGroup = 'group.com.helium.mobile.wallet.widget' @@ -274,7 +275,7 @@ const AccountsScreen = () => { - + ) }, [handleTopHeaderLayout, headerAnimatedStyle]) diff --git a/src/features/account/AirdropScreen.tsx b/src/features/account/AirdropScreen.tsx index 6a06bc9f8..9f36f881a 100644 --- a/src/features/account/AirdropScreen.tsx +++ b/src/features/account/AirdropScreen.tsx @@ -1,16 +1,22 @@ +import DripLogo from '@assets/images/dripLogo.svg' +import { ReAnimatedBox } from '@components/AnimatedBox' import BackScreen from '@components/BackScreen' import Box from '@components/Box' import ButtonPressable from '@components/ButtonPressable' -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react' -import * as solUtils from '@utils/solanaUtils' -import { useAccountStorage } from '@storage/AccountStorageProvider' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import { useTranslation } from 'react-i18next' +import CircleLoader from '@components/CircleLoader' import SafeAreaBox from '@components/SafeAreaBox' +import Text from '@components/Text' import TokenIcon from '@components/TokenIcon' -import { Edge } from 'react-native-safe-area-context' -import DripLogo from '@assets/images/dripLogo.svg' -import { ReAnimatedBox } from '@components/AnimatedBox' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' +import { NATIVE_MINT } from '@solana/spl-token' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import * as logger from '@utils/logger' +import * as solUtils from '@utils/solanaUtils' +import axios from 'axios' +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import { interpolate, runOnJS, @@ -20,12 +26,9 @@ import { withRepeat, withTiming, } from 'react-native-reanimated' -import Text from '@components/Text' -import axios from 'axios' -import * as logger from '@utils/logger' -import CircleLoader from '@components/CircleLoader' -import { HomeNavigationProp, HomeStackParamList } from '../home/homeTypes' +import { Edge } from 'react-native-safe-area-context' import { useSolana } from '../../solana/SolanaProvider' +import { HomeNavigationProp, HomeStackParamList } from '../home/homeTypes' const DROP_HEIGHT = 79 @@ -42,20 +45,22 @@ const AirdropScreen = () => { const [loading, setLoading] = useState(false) const route = useRoute() - const { ticker } = route.params + const { mint: mintStr } = route.params + const mint = usePublicKey(mintStr) + const { symbol, json } = useMetaplexMetadata(mint) const onAirdrop = useCallback(async () => { if (!currentAccount?.solanaAddress || !anchorProvider) return setLoading(true) - if (ticker === 'SOL') { + if (mint?.equals(NATIVE_MINT)) { solUtils.airdrop(anchorProvider, currentAccount?.solanaAddress) setLoading(false) navigation.goBack() } else { try { await axios.get( - `https://faucet.web.test-helium.com/${ticker.toLowerCase()}/${ + `https://faucet.web.test-helium.com/${symbol?.toLowerCase()}/${ currentAccount?.solanaAddress }?amount=2)`, ) @@ -68,7 +73,7 @@ const AirdropScreen = () => { setErrorMessage((error as Error).message) } } - }, [anchorProvider, currentAccount, navigation, ticker]) + }, [anchorProvider, currentAccount?.solanaAddress, mint, navigation, symbol]) const edges = useMemo(() => ['bottom'] as Edge[], []) @@ -163,7 +168,7 @@ const AirdropScreen = () => { marginBottom="l" > - + @@ -184,7 +189,11 @@ const AirdropScreen = () => { titleColorDisabled="black500" titleColor="primary" fontWeight="500" - title={!loading ? t('airdropScreen.airdropTicker', { ticker }) : ''} + title={ + !loading + ? t('airdropScreen.airdropTicker', { ticker: symbol || '' }) + : '' + } disabled={loading} marginVertical="l" marginHorizontal="l" diff --git a/src/features/account/TokenListItem.tsx b/src/features/account/TokenListItem.tsx index 58c4b6f0b..de399e763 100644 --- a/src/features/account/TokenListItem.tsx +++ b/src/features/account/TokenListItem.tsx @@ -6,13 +6,13 @@ import TokenIcon from '@components/TokenIcon' import TouchableContainer from '@components/TouchableContainer' import { Ticker } from '@helium/currency' import { useOwnedAmount } from '@helium/helium-react-hooks' -import { humanReadable } from '@helium/spl-utils' import useHaptic from '@hooks/useHaptic' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' import { useNavigation } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' import { useAccountStorage } from '@storage/AccountStorageProvider' +import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { useCallback, useMemo } from 'react' import { HomeNavigationProp } from '../home/homeTypes' @@ -26,11 +26,15 @@ const TokenListItem = ({ mint }: Props) => { const navigation = useNavigation() const { currentAccount } = useAccountStorage() const wallet = usePublicKey(currentAccount?.solanaAddress) - const { amount, decimals } = useOwnedAmount(wallet, mint) + const { + amount, + decimals, + loading: loadingOwned, + } = useOwnedAmount(wallet, mint) // const amount = BigInt(0) // const decimals = 0 const { triggerImpact } = useHaptic() - const { json, symbol } = useMetaplexMetadata(mint) + const { json, symbol, loading } = useMetaplexMetadata(mint) const mintStr = mint.toBase58() const handleNavigation = useCallback(() => { @@ -43,7 +47,7 @@ const TokenListItem = ({ mint }: Props) => { const balanceToDisplay = useMemo(() => { return amount && typeof decimals !== 'undefined' ? humanReadable(new BN(amount.toString()), decimals) - : '' + : '0' }, [amount, decimals]) return ( @@ -58,24 +62,46 @@ const TokenListItem = ({ mint }: Props) => { borderBottomColor="primaryBackground" borderBottomWidth={1} > - + {loading ? ( + + ) : ( + + )} + - - - {`${balanceToDisplay} `} - - - {symbol} - - + {loadingOwned ? ( + + + + + ) : ( + + + {`${balanceToDisplay} `} + + + {symbol} + + + )} {symbol && ( { const { submitDelegateDataCredits } = useSubmitTxn() const addressBookRef = useRef(null) const { - floatToBalance, networkTokensToDc, hntBalance, solBalance, @@ -88,9 +88,7 @@ const BurnScreen = () => { } = useBalance() const { showOKAlert } = useAlert() const hntKeyboardRef = useRef(null) - const [dcAmount, setDcAmount] = useState( - new Balance(Number(route.params.amount), CurrencyType.dataCredit), - ) + const [dcAmount, setDcAmount] = useState(new BN(route.params.amount)) const [submitError, setSubmitError] = useState(undefined) const [delegateAddress, setDelegateAddress] = useState(route.params.address) const [hasError, setHasError] = useState(false) @@ -118,12 +116,12 @@ const BurnScreen = () => { }, [networkType]) const amountBalance = useMemo(() => { - const amount = parseFloat(route.params.amount) + const amount = new BN(route.params.amount) if (dcAmount) return dcAmount - return floatToBalance(amount, 'HNT') - }, [floatToBalance, dcAmount, route.params.amount]) + return amount + }, [dcAmount, route.params.amount]) const feeAsTokens = useMemo(() => { return Balance.fromFloat(TXN_FEE_IN_SOL, CurrencyType.solTokens) @@ -245,7 +243,7 @@ const BurnScreen = () => { }, [amountBalance, insufficientFunds, t]) const onConfirmBalance = useCallback((opts) => { - setDcAmount(new Balance(opts.balance.floatBalance, CurrencyType.dataCredit)) + setDcAmount(new BN(opts.balance)) }, []) const handleAddressBookSelected = useCallback( diff --git a/src/features/collectables/HotspotList.tsx b/src/features/collectables/HotspotList.tsx index 5551d6715..0829780bf 100644 --- a/src/features/collectables/HotspotList.tsx +++ b/src/features/collectables/HotspotList.tsx @@ -147,7 +147,7 @@ const HotspotList = () => { const RewardItem = useCallback( ({ ticker, amount, ...rest }) => { const decimals = - ticker === 'IOT' ? iotMint?.info.decimals : mobileMint?.info.decimals + ticker === 'IOT' ? iotMint?.decimals : mobileMint?.decimals let realAmount = '' if (amount) { const num = toNumber(amount, decimals || 6) diff --git a/src/features/home/homeTypes.ts b/src/features/home/homeTypes.ts index b8068e16d..d0dd766bc 100644 --- a/src/features/home/homeTypes.ts +++ b/src/features/home/homeTypes.ts @@ -9,6 +9,7 @@ export type PaymentRouteParam = { memo?: string netType?: string defaultTokenType?: Ticker + mint?: string } export type BurnRouteParam = { @@ -35,7 +36,7 @@ export type HomeStackParamList = { action: 'payment' } PaymentScreen: undefined | PaymentRouteParam - AirdropScreen: { ticker: Ticker } + AirdropScreen: { mint: string } BurnScreen: BurnRouteParam PaymentQrScanner: undefined RequestScreen: undefined diff --git a/src/features/payment/PaymentItem.tsx b/src/features/payment/PaymentItem.tsx index e110b812c..0d5dc8f54 100644 --- a/src/features/payment/PaymentItem.tsx +++ b/src/features/payment/PaymentItem.tsx @@ -9,12 +9,12 @@ import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address from '@helium/address' import { Balance, DataCredits } from '@helium/currency' import { useMint } from '@helium/helium-react-hooks' -import { humanReadable } from '@helium/spl-utils' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { BoxProps } from '@shopify/restyle' import { PublicKey } from '@solana/web3.js' import { Theme } from '@theme/theme' import { useColors, useOpacity } from '@theme/themeHooks' +import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import { toUpper } from 'lodash' import React, { memo, useCallback, useEffect, useMemo } from 'react' @@ -85,7 +85,7 @@ const PaymentItem = ({ const { dcToNetworkTokens, oraclePrice } = useBalance() const { t } = useTranslation() const { secondaryText } = useColors() - const { symbol } = useMetaplexMetadata(mint) + const { symbol, loading: loadingMeta } = useMetaplexMetadata(mint) const addressIsWrongNetType = useMemo( () => @@ -240,17 +240,19 @@ const PaymentItem = ({ flex={1} justifyContent="center" > - - {t('payment.enterAmount', { - symbol, - })} - + {!loadingMeta && ( + + {t('payment.enterAmount', { + ticker: symbol, + })} + + )} ) : ( @@ -264,9 +266,7 @@ const PaymentItem = ({ variant="subtitle2" color="primaryText" > - {typeof amount !== 'undefined' && - typeof decimals !== 'undefined' && - humanReadable(amount, decimals)} + {humanReadable(amount, decimals)} {fee && ( diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index b82d132fa..6e22edcc6 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -20,7 +20,7 @@ import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address, { NetTypes } from '@helium/address' import { Ticker } from '@helium/currency' import { useMint, useOwnedAmount } from '@helium/helium-react-hooks' -import { HNT_MINT, humanReadable } from '@helium/spl-utils' +import { HNT_MINT } from '@helium/spl-utils' import useDisappear from '@hooks/useDisappear' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' @@ -30,7 +30,10 @@ import { PublicKey } from '@solana/web3.js' import { useVisibleTokens } from '@storage/TokensProvider' import { useColors, useHitSlop } from '@theme/themeHooks' import { Mints } from '@utils/constants' -import { calcCreateAssociatedTokenAccountAccountFee } from '@utils/solanaUtils' +import { + calcCreateAssociatedTokenAccountAccountFee, + humanReadable, +} from '@utils/solanaUtils' import BN from 'bn.js' import { unionBy } from 'lodash' import React, { @@ -88,7 +91,8 @@ const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { { payee: opts.payee, amount: opts.amount, - mint: Mints[opts.defaultTokenType?.toUpperCase() as Ticker], + mint: + opts.mint || Mints[opts.defaultTokenType?.toUpperCase() as Ticker], }, ] } @@ -167,11 +171,19 @@ const PaymentScreen = () => { netType: networkType, }) + useEffect(() => { + dispatch({ + type: 'updateTokenBalance', + balance, + }) + }, [dispatch, balance]) + const { submitPayment } = useSubmitTxn() const solanaPayment = useSelector( (reduxState: RootState) => reduxState.solana.payment, ) + const { symbol } = useMetaplexMetadata(mint) const { top } = useSafeAreaInsets() @@ -366,7 +378,9 @@ const PaymentScreen = () => { } if (insufficientFunds[0]) { errStrings.push( - t('payment.insufficientFunds', { token: insufficientFunds[1] }), + t('payment.insufficientFunds', { + token: insufficientFunds[1]?.equals(NATIVE_MINT) ? 'SOL' : symbol, + }), ) } @@ -379,12 +393,13 @@ const PaymentScreen = () => { } return errStrings }, [ - currentAccount, + currentAccount?.ledgerDevice, + paymentState.payments.length, insufficientFunds, selfPay, - paymentState.payments.length, - t, wrongNetTypePay, + t, + symbol, ]) const isFormValid = useMemo(() => { @@ -593,11 +608,8 @@ const PaymentScreen = () => { }, [sortedAccountsForNetType]) const decimals = useMint(mint)?.info?.decimals - const { symbol } = useMetaplexMetadata(mint) const tokenButtonBalance = useMemo(() => { - if (typeof balance !== 'undefined' && typeof decimals !== 'undefined') { - return humanReadable(balance, decimals) - } + return humanReadable(balance, decimals) }, [balance, decimals]) const data = useMemo((): TokenListItem[] => { diff --git a/src/features/payment/PaymentSummary.tsx b/src/features/payment/PaymentSummary.tsx index 553c7251c..429091bf7 100644 --- a/src/features/payment/PaymentSummary.tsx +++ b/src/features/payment/PaymentSummary.tsx @@ -2,8 +2,8 @@ import AccountIcon from '@components/AccountIcon' import Box from '@components/Box' import Text from '@components/Text' import { useMint } from '@helium/helium-react-hooks' -import { humanReadable } from '@helium/spl-utils' import { PublicKey } from '@solana/web3.js' +import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' @@ -34,10 +34,7 @@ const PaymentSummary = ({ const decimals = useMint(mint)?.info?.decimals const total = useMemo(() => { - if (typeof totalBalance === 'undefined' || typeof decimals === 'undefined') - return '' - - return humanReadable(totalBalance, decimals) + return humanReadable(totalBalance, decimals) || '' }, [totalBalance, decimals]) const fee = useMemo( () => diff --git a/src/features/payment/usePaymentsReducer.ts b/src/features/payment/usePaymentsReducer.ts index 098bc5f00..9738c2727 100644 --- a/src/features/payment/usePaymentsReducer.ts +++ b/src/features/payment/usePaymentsReducer.ts @@ -24,6 +24,11 @@ type UpdateBalanceAction = { payer: string } +type UpdateTokenBalanceAction = { + type: 'updateTokenBalance' + balance?: BN +} + type RemovePayment = { type: 'removePayment' index: number @@ -81,9 +86,9 @@ const initialState = (opts: { error: undefined, payments: [{}] as Array, totalAmount: new BN(0), - balance: opts.balance || new BN(0), ...calculateFee([{}]), ...opts, + balance: opts.balance || new BN(0), }) const paymentsSum = (payments: Payment[]) => { @@ -143,6 +148,7 @@ function reducer( action: | UpdatePayeeAction | UpdateBalanceAction + | UpdateTokenBalanceAction | UpdateErrorAction | AddPayee | AddLinkedPayments @@ -210,6 +216,13 @@ function reducer( }) return { ...state, ...recalculate(nextPayments, state) } } + + case 'updateTokenBalance': { + return { + ...state, + balance: action.balance || new BN(0), + } + } case 'addPayee': { if (state.payments.length >= MAX_PAYMENTS) return state diff --git a/src/features/request/RequestScreen.tsx b/src/features/request/RequestScreen.tsx index c86147064..85ec79e17 100644 --- a/src/features/request/RequestScreen.tsx +++ b/src/features/request/RequestScreen.tsx @@ -17,7 +17,6 @@ import TokenSelector, { import TouchableOpacityBox from '@components/TouchableOpacityBox' import { NetTypes as NetType } from '@helium/address' import { useMint } from '@helium/helium-react-hooks' -import { humanReadable } from '@helium/spl-utils' import useHaptic from '@hooks/useHaptic' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import Clipboard from '@react-native-community/clipboard' @@ -34,6 +33,7 @@ import { } from '@theme/themeHooks' import animateTransition from '@utils/animateTransition' import { makePayRequestLink } from '@utils/linking' +import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { memo, @@ -339,8 +339,7 @@ const RequestScreen = () => { ) : ( - {typeof decimals !== 'undefined' && - humanReadable(paymentAmount, decimals)} + {humanReadable(paymentAmount, decimals)} )} diff --git a/src/hooks/useSimulatedTransaction.ts b/src/hooks/useSimulatedTransaction.ts index 88d121a45..1e9e3a21c 100644 --- a/src/hooks/useSimulatedTransaction.ts +++ b/src/hooks/useSimulatedTransaction.ts @@ -25,7 +25,7 @@ type BalanceChange = { nativeChange?: number mint?: PublicKey symbol?: string - type?: 'send' | 'recieve' + type?: 'send' | 'receive' } type BalanceChanges = BalanceChange[] | null @@ -301,7 +301,7 @@ export function useSimulatedTransaction( const type = accountNativeBalance.lt(existingNativeBalance) ? 'send' - : 'recieve' + : 'receive' // Filter out zero change if (!accountNativeBalance.eq(existingNativeBalance)) { diff --git a/src/locales/en.ts b/src/locales/en.ts index 095354c13..4a7c3e765 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -382,7 +382,7 @@ export default { connectToWebsitesYouTrust: 'Only connect to websites you trust', estimatedChanges: 'Estimated Changes', sendToken: 'Send {{amount}} {{ticker}}', - recieveToken: 'Receive {{amount}} {{ticker}}', + receiveToken: 'Receive {{amount}} {{ticker}}', insufficientFunds: 'Insufficient funds', insufficientRentExempt: 'Solana wallets must have a minimum of ~{{amount}} SOL to cover rent. The result of this transaction would leave your wallet with less than the rent-exempt minimum.', diff --git a/src/solana/WalletSignBottomSheet.tsx b/src/solana/WalletSignBottomSheet.tsx index 3913002e5..fec09339b 100644 --- a/src/solana/WalletSignBottomSheet.tsx +++ b/src/solana/WalletSignBottomSheet.tsx @@ -1,7 +1,25 @@ +import Checkmark from '@assets/images/checkmark.svg' +import Box from '@components/Box' +import ButtonPressable from '@components/ButtonPressable' +import SafeAreaBox from '@components/SafeAreaBox' +import Text from '@components/Text' +import { + BottomSheetBackdrop, + BottomSheetModal, + BottomSheetModalProvider, + useBottomSheetDynamicSnapPoints, +} from '@gorhom/bottom-sheet' +import { useSolOwnedAmount } from '@helium/helium-react-hooks' +import { usePublicKey } from '@hooks/usePublicKey' +import { useRentExempt } from '@hooks/useRentExempt' +import { LAMPORTS_PER_SOL } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { useColors, useOpacity } from '@theme/themeHooks' +import BN from 'bn.js' import React, { + Ref, forwardRef, memo, - Ref, useCallback, useEffect, useImperativeHandle, @@ -9,31 +27,16 @@ import React, { useRef, useState, } from 'react' -import Checkmark from '@assets/images/checkmark.svg' import { useTranslation } from 'react-i18next' -import { - BottomSheetBackdrop, - useBottomSheetDynamicSnapPoints, - BottomSheetModal, - BottomSheetModalProvider, -} from '@gorhom/bottom-sheet' -import { Edge } from 'react-native-safe-area-context' -import SafeAreaBox from '@components/SafeAreaBox' -import Box from '@components/Box' -import Text from '@components/Text' -import { useColors, useOpacity } from '@theme/themeHooks' -import ButtonPressable from '@components/ButtonPressable' -import { LAMPORTS_PER_SOL } from '@solana/web3.js' -import { useBalance } from '@utils/Balance' import { ScrollView } from 'react-native-gesture-handler' -import { useRentExempt } from '@hooks/useRentExempt' +import { Edge } from 'react-native-safe-area-context' +import WalletSignBottomSheetTransaction from './WalletSignBottomSheetTransaction' import { - WalletSignBottomSheetRef, WalletSignBottomSheetProps, + WalletSignBottomSheetRef, WalletSignOpts, WalletStandardMessageTypes, } from './walletSignBottomSheetTypes' -import WalletSignBottomSheetTransaction from './WalletSignBottomSheetTransaction' let promiseResolve: (value: boolean | PromiseLike) => void @@ -47,7 +50,9 @@ const WalletSignBottomSheet = forwardRef( const { backgroundStyle } = useOpacity('surfaceSecondary', 1) const { secondaryText } = useColors() const { t } = useTranslation() - const { solBalance } = useBalance() + const { currentAccount } = useAccountStorage() + const solanaAddress = usePublicKey(currentAccount?.solanaAddress) + const { amount: solBalance } = useSolOwnedAmount(solanaAddress) const bottomSheetModalRef = useRef(null) const [totalSolFee, setTotalSolFee] = useState(0) const [isVisible, setIsVisible] = useState(false) @@ -96,22 +101,21 @@ const WalletSignBottomSheet = forwardRef( return 5000 / LAMPORTS_PER_SOL }, [walletSignOpts, totalSolFee, currentTxs]) - const insufficientRentExempt = useMemo( - () => - (solBalance?.floatBalance || 0) - estimatedTotalSolByLamports < - (rentExempt || 0), - [solBalance?.floatBalance, estimatedTotalSolByLamports, rentExempt], - ) + const insufficientRentExempt = useMemo(() => { + if (solBalance) { + return new BN(solBalance.toString()) + .sub(new BN(estimatedTotalSolByLamports)) + .lt(new BN(rentExempt || 0)) + } + }, [solBalance, estimatedTotalSolByLamports, rentExempt]) const insufficientFunds = useMemo( () => nestedInsufficentFunds || - estimatedTotalSolByLamports > (solBalance?.floatBalance || 0), - [ - solBalance?.floatBalance, - estimatedTotalSolByLamports, - nestedInsufficentFunds, - ], + new BN(estimatedTotalSolByLamports).gt( + new BN(solBalance?.toString() || '0'), + ), + [solBalance, estimatedTotalSolByLamports, nestedInsufficentFunds], ) const safeEdges = useMemo(() => ['bottom'] as Edge[], []) diff --git a/src/solana/WalletSignBottomSheetTransaction.tsx b/src/solana/WalletSignBottomSheetTransaction.tsx index ca54e9a31..fde0b60e8 100644 --- a/src/solana/WalletSignBottomSheetTransaction.tsx +++ b/src/solana/WalletSignBottomSheetTransaction.tsx @@ -100,7 +100,7 @@ const WalletSignBottomSheetTransaction = ({ amount: change.nativeChange, }) } else { - balanceChange = t('browserScreen.recieveToken', { + balanceChange = t('browserScreen.receiveToken', { ticker: change.symbol, amount: change.nativeChange, }) diff --git a/src/storage/TokensProvider.tsx b/src/storage/TokensProvider.tsx index 0fa3bc11c..d1df42d69 100644 --- a/src/storage/TokensProvider.tsx +++ b/src/storage/TokensProvider.tsx @@ -16,7 +16,7 @@ import { updateVisibleTokens, } from './cloudStorage' -const DEFAULT_TOKENS = new Set([ +export const DEFAULT_TOKENS = new Set([ HNT_MINT.toBase58(), MOBILE_MINT.toBase58(), IOT_MINT.toBase58(), @@ -38,7 +38,7 @@ const useVisibleTokensHook = () => { if (response) { setVisibleTokens( Object.entries(response).reduce((acc, [key, s]) => { - acc[key] = new Set(s) + acc[key] = new Set([...s, ...DEFAULT_TOKENS]) return acc }, {} as Record>), ) diff --git a/src/store/slices/balancesSlice.ts b/src/store/slices/balancesSlice.ts index 35aaa539b..2cc7208b5 100644 --- a/src/store/slices/balancesSlice.ts +++ b/src/store/slices/balancesSlice.ts @@ -1,12 +1,10 @@ import { AnchorProvider } from '@coral-xyz/anchor' import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit' -import { Cluster, PublicKey } from '@solana/web3.js' import { AccountLayout, TOKEN_PROGRAM_ID, getMint } from '@solana/spl-token' -import BN from 'bn.js' +import { Cluster, PublicKey } from '@solana/web3.js' import { CSAccount } from '../../storage/cloudStorage' -import { getBalanceHistory, getTokenPrices } from '../../utils/walletApiV2' import { AccountBalance, Prices, TokenAccount } from '../../types/balance' -import { getEscrowTokenAccount } from '../../utils/solanaUtils' +import { getBalanceHistory, getTokenPrices } from '../../utils/walletApiV2' type BalanceHistoryByCurrency = Record type BalanceHistoryByWallet = Record @@ -15,7 +13,6 @@ type BalanceHistoryByCluster = Record export type Tokens = { atas: TokenAccount[] sol: { tokenAccount: string; balance: number } - dcEscrow: { tokenAccount: string; balance: number } } type AtaBalances = Record> @@ -59,6 +56,7 @@ export const syncTokenAccounts = createAsyncThunk( const tokenAccounts = await connection.getTokenAccountsByOwner(pubKey, { programId: TOKEN_PROGRAM_ID, }) + const solAcct = await connection.getAccountInfo(pubKey) const atas = await Promise.all( tokenAccounts.value.map(async (tokenAccount) => { @@ -75,26 +73,7 @@ export const syncTokenAccounts = createAsyncThunk( }), ) - const escrowAccount = getEscrowTokenAccount(acct.solanaAddress) - let escrowBalance = 0 - const [dcEscrowAcc, solAcc] = await Promise.all([ - connection.getAccountInfo(escrowAccount), - connection.getAccountInfo(pubKey), - ]) - try { - const dcEscrowBalance = - dcEscrowAcc && AccountLayout.decode(dcEscrowAcc.data).amount - escrowBalance = dcEscrowBalance - ? new BN(dcEscrowBalance.toString()).toNumber() - : 0 - } catch {} - - const dcEscrow = { - tokenAccount: escrowAccount.toBase58(), - balance: escrowBalance, - } - - const solBalance = solAcc?.lamports || 0 + const solBalance = solAcct?.lamports || 0 const sol = { tokenAccount: acct.solanaAddress, balance: solBalance, @@ -102,7 +81,6 @@ export const syncTokenAccounts = createAsyncThunk( return { atas, - dcEscrow, sol, } }, @@ -144,24 +122,16 @@ const balancesSlice = createSlice({ cluster: Cluster solanaAddress: string balance: number - type: 'dcEscrow' | 'sol' tokenAccount: string }>, ) => { const { payload } = action - const { cluster, solanaAddress, balance, type, tokenAccount } = payload + const { cluster, solanaAddress, balance, tokenAccount } = payload const next = { tokenAccount, balance } const prevTokens = state.balances?.[cluster]?.[solanaAddress] if (!prevTokens) return - switch (type) { - case 'dcEscrow': - prevTokens.dcEscrow = next - break - case 'sol': - prevTokens.sol = next - break - } + prevTokens.sol = next }, updateAtaBalance: ( state, @@ -234,5 +204,5 @@ const balancesSlice = createSlice({ }) const { reducer, name } = balancesSlice -export { name, balancesSlice } +export { balancesSlice, name } export default reducer diff --git a/src/store/store.ts b/src/store/store.ts index 163dcfbea..97ad74d3a 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -33,6 +33,7 @@ const store = configureStore({ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, + immutableCheck: { warnAfter: 500 }, }).concat([solanaStatusApi.middleware]), enhancers, }) diff --git a/src/types/balance.ts b/src/types/balance.ts index 636c738ae..46fc75577 100644 --- a/src/types/balance.ts +++ b/src/types/balance.ts @@ -34,8 +34,6 @@ export type Prices = Record> export type BalanceInfo = { atas: Required[] dcBalance: Balance - dcEscrowBalance: Balance - dcEscrowToken: Omit formattedDcValue: string formattedEscrowDcValue: string formattedHntValue: string diff --git a/src/utils/Balance.tsx b/src/utils/Balance.tsx index 8870e1c99..06ecbe510 100644 --- a/src/utils/Balance.tsx +++ b/src/utils/Balance.tsx @@ -35,7 +35,6 @@ import { useAppDispatch } from '../store/store' import { AccountBalance, BalanceInfo, TokenAccount } from '../types/balance' import StoreAtaBalance from './StoreAtaBalance' import StoreSolBalance from './StoreSolBalance' -import StoreTokenBalance from './StoreTokenBalance' import { accountCurrencyType } from './accountUtils' import { decimalSeparator, groupSeparator } from './i18n' import { useBalanceHistory } from './useBalanceHistory' @@ -214,17 +213,6 @@ const useBalanceHook = () => { const solToken = accountBalancesForCluster?.sol - const dcEscrowToken = accountBalancesForCluster?.dcEscrow - - const dcEscrowBalance = new Balance( - dcEscrowToken?.balance || 0, - CurrencyType.dataCredit, - ) - const formattedEscrowDcValue = await CurrencyFormatter.format( - dcEscrowBalance.toUsd(oraclePrice).floatBalance, - 'usd', - ) - const solBalance = Balance.fromIntAndTicker(solToken?.balance || 0, 'SOL') const solPrice = tokenPrices?.solana?.[currency] || 0 const solAmount = solBalance?.floatBalance @@ -277,11 +265,7 @@ const useBalanceHook = () => { return { atas, - dcBalance, - dcEscrowBalance, - dcEscrowToken, formattedDcValue, - formattedEscrowDcValue, formattedHntValue, formattedIotValue, formattedMobileValue, @@ -395,9 +379,6 @@ const useBalanceHook = () => { const initialState = { balanceHistory: [] as AccountBalance[], bonesToBalance: () => new Balance(0, CurrencyType.networkToken), - dcBalance: new Balance(0, CurrencyType.dataCredit), - dcDelegatedBalance: new Balance(0, CurrencyType.dataCredit), - dcEscrowBalance: new Balance(0, CurrencyType.dataCredit), dcToNetworkTokens: () => undefined, floatToBalance: () => undefined, formattedDcValue: '', @@ -423,7 +404,6 @@ const initialState = { atas: [], updating: false, solToken: undefined, - dcEscrowToken: undefined, tokenAccounts: undefined, } const BalanceContext = @@ -433,7 +413,7 @@ const { Provider } = BalanceContext export const BalanceProvider = ({ children }: { children: ReactNode }) => { const balanceHook = useBalanceHook() - const { atas, dcEscrowToken, solToken } = balanceHook + const { atas, solToken } = balanceHook const { cluster } = useSolana() const prevSolAddress = usePrevious(solToken?.tokenAccount) const prevCluster = usePrevious(cluster) @@ -452,12 +432,6 @@ export const BalanceProvider = ({ children }: { children: ReactNode }) => { {atas?.map((ta) => ( ))} - {dcEscrowToken?.tokenAccount && ( - - )} {solToken?.tokenAccount && ( )} diff --git a/src/utils/StoreSolBalance.ts b/src/utils/StoreSolBalance.ts index a0fe5a8ff..92cf74464 100644 --- a/src/utils/StoreSolBalance.ts +++ b/src/utils/StoreSolBalance.ts @@ -23,7 +23,6 @@ const StoreSolBalance = ({ solanaAddress }: Props) => { cluster, solanaAddress, balance: Number(amount), - type: 'sol', tokenAccount: solanaAddress, }), ) diff --git a/src/utils/StoreTokenBalance.ts b/src/utils/StoreTokenBalance.ts index 9a345eaac..25d29f74d 100644 --- a/src/utils/StoreTokenBalance.ts +++ b/src/utils/StoreTokenBalance.ts @@ -1,18 +1,17 @@ import { useTokenAccount } from '@helium/helium-react-hooks' +import { AccountLayout } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' import { useEffect } from 'react' -import { AccountLayout } from '@solana/spl-token' +import { useSolana } from '../solana/SolanaProvider' import { useAccountStorage } from '../storage/AccountStorageProvider' import { balancesSlice } from '../store/slices/balancesSlice' import { useAppDispatch } from '../store/store' -import { useSolana } from '../solana/SolanaProvider' type TokenInput = { tokenAccount: string - type: 'dcEscrow' | 'sol' } -const StoreTokenBalance = ({ tokenAccount, type }: TokenInput) => { +const StoreTokenBalance = ({ tokenAccount }: TokenInput) => { const tokenAccountResponse = useTokenAccount(new PublicKey(tokenAccount)) const { currentAccount } = useAccountStorage() const dispatch = useAppDispatch() @@ -39,7 +38,6 @@ const StoreTokenBalance = ({ tokenAccount, type }: TokenInput) => { cluster, solanaAddress: currentAccount?.solanaAddress, balance: amount, - type, tokenAccount, }), ) @@ -49,7 +47,6 @@ const StoreTokenBalance = ({ tokenAccount, type }: TokenInput) => { dispatch, tokenAccount, tokenAccountResponse, - type, ]) return null diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 314b0c3ae..9843531aa 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -118,6 +118,30 @@ import { import { getH3Location } from './h3' import * as Logger from './logger' import sleep from './sleep' +import { decimalSeparator, groupSeparator } from './i18n' + +export function humanReadable( + amount?: BN, + decimals?: number, +): string | undefined { + if (typeof decimals === 'undefined' || typeof amount === 'undefined') return + + const input = amount.toString() + const integerPart = + input.length > decimals ? input.slice(0, input.length - decimals) : '' + const formattedIntegerPart = integerPart.replace( + /\B(?=(\d{3})+(?!\d))/g, + groupSeparator, + ) + const decimalPart = input + .slice(-decimals) + .padStart(decimals, '0') // Add prefix zeros + .replace(/0+$/, '') // Remove trailing zeros + + return `${formattedIntegerPart.length > 0 ? formattedIntegerPart : '0'}${ + Number(decimalPart) !== 0 ? `${decimalSeparator}${decimalPart}` : '' + }` +} const govProgramId = new PublicKey( 'hgovkRU6Ghe1Qoyb54HdSLdqN7VtxaifBzRmh9jtd3S', @@ -655,16 +679,20 @@ export const delegateDataCredits = async ( } } -export const getEscrowTokenAccount = (address: string) => { - try { - const subDao = IOT_SUB_DAO_KEY - const delegatedDataCredits = delegatedDataCreditsKey(subDao, address)[0] - const escrowTokenAccount = escrowAccountKey(delegatedDataCredits)[0] - - return escrowTokenAccount - } catch (e) { - Logger.error(e) - throw e as Error +export const getEscrowTokenAccount = ( + address: string | undefined, + subDao: PublicKey, +) => { + if (address) { + try { + const delegatedDataCredits = delegatedDataCreditsKey(subDao, address)[0] + const escrowTokenAccount = escrowAccountKey(delegatedDataCredits)[0] + + return escrowTokenAccount + } catch (e) { + Logger.error(e) + throw e as Error + } } } @@ -1633,7 +1661,9 @@ export const updateEntityInfoTxn = async ({ ) const mobileInfo = await program.account.mobileHotspotInfoV0.fetchNullable( - mobileInfoKey(mobileConfigKey, entityKey)[0], + ( + await mobileInfoKey(mobileConfigKey, entityKey) + )[0], ) if (!mobileInfo) { diff --git a/yarn.lock b/yarn.lock index 8ae1a13ee..27041af85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2275,13 +2275,6 @@ dependencies: "@solana/web3.js" "^1.43.4" -"@helium/account-fetch-cache@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.5.tgz#518e945abd51bad1811ff0b4b5c80d62ebafdb6f" - integrity sha512-Cv8ST15awWpaHJGkY3SGeb+BuZ0wzbS3AMtQ8ZAeUr+XuJ5mbaGZUE9QPI9dNPKV1txzgn4JkEs+BQywt8LwRg== - dependencies: - "@solana/web3.js" "^1.43.4" - "@helium/address@4.6.2": version "4.6.2" resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.6.2.tgz#0356bd9693ff7184f93f3c4e12c04f4f9c1db7b6" @@ -2291,15 +2284,6 @@ js-sha256 "^0.9.0" multiformats "^9.6.4" -"@helium/address@^4.10.2": - version "4.10.2" - resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.10.2.tgz#56960b118fceb6b6ddabe3e4ecec467d9ae50e26" - integrity sha512-qCswC7Z3GXuJyHv36RcOSnffeghjqJQx0fdu2Lxpf9fgOnIi1JZO2tjjk1mBaqOwCyp+0YzrTPUoEukL/WCtsA== - dependencies: - bs58 "^5.0.0" - js-sha256 "^0.9.0" - multiformats "^9.6.4" - "@helium/address@^4.6.2", "@helium/address@^4.8.0", "@helium/address@^4.8.1": version "4.8.1" resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.8.1.tgz#d8d7cefc6aa7791d79eb8759befb821aaccec3ff" @@ -2317,6 +2301,18 @@ "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.43.4" +"@helium/circuit-breaker-sdk@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.0.32.tgz#e7fbafdadae1078290b8d3f0b53b81bf7b4628e7" + integrity sha512-dFKFc2UehuTVHKANWXX8GUfkmmKqLQX5aOX5+Ngm5aWkgZSkGZEbjl1G/ke8B6xwH3sm+fe19ID+mtBseuk2mw== + dependencies: + "@coral-xyz/anchor" "^0.26.0" + "@helium/idls" "^0.0.32" + "@helium/spl-utils" "^0.0.32" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + "@helium/circuit-breaker-sdk@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.1.2.tgz#b7f684ba1a5dbc36027a66b85c8914a7ca0e43c7" @@ -2329,6 +2325,18 @@ bn.js "^5.2.0" bs58 "^4.0.1" +"@helium/circuit-breaker-sdk@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.1.4.tgz#53a49a70d533540e4118c19f2d04cd63c556b390" + integrity sha512-9Fa1zxhYO9Nh+iZuPgA4dpyAp9Le2TRoVRu/caWWDC8DNC9Ba2Hd/xeWbRDExymryVZqq741U57OiAi3cPXwbQ== + dependencies: + "@coral-xyz/anchor" "^0.26.0" + "@helium/idls" "^0.1.1" + "@helium/spl-utils" "^0.1.4" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + "@helium/circuit-breaker-sdk@^0.2.14": version "0.2.14" resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.14.tgz#a325cf881f8dec6b7f398a4a08f8a74e70610d63" @@ -2387,23 +2395,18 @@ bs58 "^4.0.1" crypto-js "^4.1.1" -"@helium/distributor-oracle@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/distributor-oracle/-/distributor-oracle-0.2.14.tgz#677dccd5167333adea2cc2d777c66d677b330568" - integrity sha512-2ecSvRYVvWPh48mkh6mLnCgghlZVDcFDHE/KLKermCR5HUFrATFuNmFGyDMMCzUZ4qufM915iK76X1/SgdyCsA== +"@helium/distributor-oracle@file:.yalc/@helium/distributor-oracle": + version "0.1.2" dependencies: "@coral-xyz/anchor" "^0.26.0" "@fastify/cors" "^8.1.1" - "@helium/account-fetch-cache" "^0.2.14" - "@helium/address" "^4.10.2" - "@helium/helium-entity-manager-sdk" "^0.2.14" - "@helium/helium-sub-daos-sdk" "^0.2.14" - "@helium/idls" "^0.2.5" - "@helium/lazy-distributor-sdk" "^0.2.14" - "@helium/rewards-oracle-sdk" "^0.2.14" - "@helium/spl-utils" "^0.2.14" - "@metaplex-foundation/mpl-bubblegum" "^0.7.0" - "@solana/spl-token" "^0.3.8" + "@helium/address" "^4.6.2" + "@helium/helium-entity-manager-sdk" "^0.1.2" + "@helium/helium-sub-daos-sdk" "^0.1.2" + "@helium/idls" "^0.1.1" + "@helium/lazy-distributor-sdk" "^0.1.2" + "@helium/rewards-oracle-sdk" "^0.1.1" + "@helium/spl-utils" "^0.1.2" "@types/sequelize" "^4.28.14" aws-sdk "^2.1313.0" axios "^0.27.2" @@ -2428,26 +2431,44 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/helium-entity-manager-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.2.14.tgz#f2a864797daf03a32f6da52f6d9ab56c8dc259c1" - integrity sha512-mWZ10jo1MxwpeB+Mw9JZ5kmRU6gYwJSHUisCuwsi/0OSnZI8l9QlRGsM3HrLA9RnuCeUD+2h+/56wdtF9esTmg== +"@helium/helium-entity-manager-sdk@^0.1.2": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.1.5.tgz#96457885d125c781831a1b595b3cb5e3d6a91f1d" + integrity sha512-1zvavQVXDXmH5IFKYJJwGPxV4iMPQYiGJKk6do1jGFFizfGelDD7raUq+csnMiNExYQeOT8vQSyjNpQuYKjU2g== dependencies: - "@coral-xyz/anchor" "^0.26.0" - "@helium/address" "^4.10.2" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/helium-sub-daos-sdk" "^0.2.14" - "@helium/idls" "^0.2.5" - "@helium/spl-utils" "^0.2.14" + "@coral-xyz/anchor" "0.26.0" + "@helium/address" "^4.6.2" + "@helium/helium-sub-daos-sdk" "^0.1.4" + "@helium/idls" "^0.1.1" + "@helium/spl-utils" "^0.1.4" + "@metaplex-foundation/mpl-bubblegum" "^0.6.2" + "@metaplex-foundation/mpl-token-metadata" "^2.2.3" + "@solana/spl-account-compression" "^0.1.5" + "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" js-sha256 "^0.9.0" -"@helium/helium-react-hooks@0.2.14": +"@helium/helium-entity-manager-sdk@file:.yalc/@helium/helium-entity-manager-sdk": + version "0.0.32" + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/address" "^4.6.2" + "@helium/helium-sub-daos-sdk" "^0.0.32" + "@helium/idls" "^0.0.32" + "@helium/spl-utils" "^0.0.32" + "@metaplex-foundation/mpl-bubblegum" "^0.6.2" + "@metaplex-foundation/mpl-token-metadata" "^2.2.3" + "@solana/spl-account-compression" "^0.1.5" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + crypto-js "^4.1.1" + js-sha256 "^0.9.0" + +"@helium/helium-react-hooks@file:.yalc/@helium/helium-react-hooks": version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/helium-react-hooks/-/helium-react-hooks-0.2.14.tgz#82eead80f801ae9b0a46daca3fae38860d22dd35" - integrity sha512-vtkA6YVVoARm+WD50x7/jvUWg/s64KnIS+Gl2UVx6otsgtdPWKEM/iEAG4z/I6Ri1z/q+yLUOnjps5xuuKBToA== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/account-fetch-cache" "^0.2.14" @@ -2471,6 +2492,32 @@ bn.js "^5.2.0" bs58 "^4.0.1" +"@helium/helium-sub-daos-sdk@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.0.32.tgz#0141a03a67e67fc63fc95e12d2983d5476f2e348" + integrity sha512-1jGpoVSuqJAP+omnRXet0EVJlooP/pulml4x1hbE3XtCFOhU0Ev31RFmoAuuVZmpuKdP8CtUzyEjQpOzO2yqsA== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/circuit-breaker-sdk" "^0.0.32" + "@helium/spl-utils" "^0.0.32" + "@helium/treasury-management-sdk" "^0.0.32" + "@helium/voter-stake-registry-sdk" "^0.0.32" + bn.js "^5.2.0" + bs58 "^4.0.1" + +"@helium/helium-sub-daos-sdk@^0.1.2", "@helium/helium-sub-daos-sdk@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.1.4.tgz#99852310f0f8fa4e7afa2d128f3d2eff884b65d4" + integrity sha512-O7OiEYrZeLBHJJAdzPuG3JygrZ4i+cb3l5QnyQ+pIVpunuOfsA+fNpzgzDH2MBE9MDUkOr3kR3uSF3Jy3DA9ww== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/circuit-breaker-sdk" "^0.1.4" + "@helium/spl-utils" "^0.1.4" + "@helium/treasury-management-sdk" "^0.1.4" + "@helium/voter-stake-registry-sdk" "^0.1.4" + bn.js "^5.2.0" + bs58 "^4.0.1" + "@helium/http@4.7.5": version "4.7.5" resolved "https://registry.yarnpkg.com/@helium/http/-/http-4.7.5.tgz#c78ffcba77d29b9ef5514adfd41c08412ee8a8d3" @@ -2484,6 +2531,17 @@ retry-axios "^2.1.2" snakecase-keys "^5.1.0" +"@helium/idls@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.0.32.tgz#5b5ce1b8fabfbe6494d1fa0b1eb202dd19f4e2c6" + integrity sha512-74s0qqHdwk/X5iWPcGxytOIsrjeLo4xB87koy99/2Tilht085pu0RdEnM9TwTg3Nx7zaRTcr82yt1cjOySoCFg== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@solana/web3.js" "^1.43.4" + bn.js "^5.2.0" + borsh "^0.7.0" + bs58 "^4.0.1" + "@helium/idls@^0.0.43": version "0.0.43" resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.0.43.tgz#de77dccd27411f6f2eed6daabb8b1ea1f600fe19" @@ -2517,27 +2575,27 @@ borsh "^0.7.0" bs58 "^4.0.1" -"@helium/lazy-distributor-sdk@0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.1.2.tgz#fba4313826eee6ce199143104bbd502c29d25711" - integrity sha512-kAFexiDHjGbEpm2H+C/8omOlu+0pClX61js8eslZxJgtWoLjMa+iwWNp2CPILDplsP/pKJyMPEHFHW67+ezCMQ== +"@helium/lazy-distributor-sdk@^0.1.2": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.1.4.tgz#f221cb946bb7730c6422c901d56a0588c92f6b10" + integrity sha512-8Ouh1lqmi7Tt6m3Vr3NNrxK/6VJ9qs6w9Aj/PUjhc8oc8uaOCJ4qrwNLwq4fHogsn9XduE/JBKROGCyL9iOFEQ== dependencies: "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.2" - "@helium/spl-utils" "^0.1.2" + "@helium/circuit-breaker-sdk" "^0.1.4" + "@helium/spl-utils" "^0.1.4" "@metaplex-foundation/mpl-token-metadata" "^2.2.3" "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/lazy-distributor-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.2.14.tgz#5989317a2ef1aed0235b21dfdd73fa5307227604" - integrity sha512-+3hNVpwOV0PSBgysunEZYI7rHXB1fLq3n3BbVva7kb0gm0QAwJcoI2NPVn98jI3x1jCgQnw0SYAPUFAUiYV0nw== +"@helium/lazy-distributor-sdk@file:.yalc/@helium/lazy-distributor-sdk": + version "0.1.2" dependencies: - "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.14" + "@coral-xyz/anchor" "0.26.0" + "@helium/circuit-breaker-sdk" "^0.1.2" + "@helium/spl-utils" "^0.1.2" + "@metaplex-foundation/mpl-token-metadata" "^2.2.3" + "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2569,17 +2627,44 @@ resolved "https://registry.yarnpkg.com/@helium/react-native-sdk/-/react-native-sdk-1.0.0.tgz#41024fa99859490bd8a0b717f52acc11ae72f114" integrity sha512-Qi1Nnp/q2hsz2D7aeuM6LxXhNX8NrHz1U+PoQslwK2XfqPFZEYb4uAzjXDKlc+JBWPiF96GMJywv/ofxlZ9XLg== -"@helium/rewards-oracle-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.2.14.tgz#8422914271821b43850572da677866870adabdeb" - integrity sha512-VE4FJy43tvvIdEoJBHch2+BlQzeBaDdcxzOZPXftVM9gxL0sHxlAKRXsozgg6eRB3mowMKd6EHa4FxJztgMBMA== +"@helium/rewards-oracle-sdk@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.1.1.tgz#b056b73e0a0b556b22c3190ece80aa5d52fe62c1" + integrity sha512-Qo5WZ+isTaznv3KNK92V44h7s+AWcD4de/J4pR7Gekii1F9p+0uY/AQAjHyXTUMxBcZu9UxZK19xtUnt5R4K5A== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" "@helium/idls" "^0.0.43" + "@helium/spl-utils" "^0.0.43" + "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" bs58 "^4.0.1" +"@helium/spl-utils@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.0.32.tgz#f665611767b398eb2d6024d9b9e5ddbee40e9191" + integrity sha512-VWp3Ve02X2fOd49Nojfw4yDDFH/Iqi/iZ9AbeTJMpfWSQsixFULVeBGADQQFa4xiIYynI3x+zX5Uo1wzgZuxlw== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/address" "^4.8.1" + "@solana/spl-token" "^0.3.6" + "@solana/web3.js" "^1.43.4" + bn.js "^5.2.0" + borsh "^0.7.0" + bs58 "^5.0.0" + +"@helium/spl-utils@^0.0.43": + version "0.0.43" + resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.0.43.tgz#7b5d7266e5ea56f5fbb3e7635831378f90f90a8a" + integrity sha512-RfETETah5MtKVoZqMmIzeMTgpFdL6bfs1e+7+2U8zcmGPyi8zMNyRDyRWtQZiknkbBtpORfgJEaP3TXwQBaxSQ== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/address" "^4.8.1" + "@solana/spl-token" "^0.3.6" + "@solana/web3.js" "^1.43.4" + bn.js "^5.2.0" + borsh "^0.7.0" + bs58 "^5.0.0" + "@helium/spl-utils@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.1.2.tgz#e12b924bf4bd3217f265250a2720cb7ed2316d1d" @@ -2594,16 +2679,13 @@ borsh "^0.7.0" bs58 "^5.0.0" -"@helium/spl-utils@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.14.tgz#7d9bcc8236095d81c9cea661c9ca32741cb70bf6" - integrity sha512-joRHzlFppePOymHQ8GpAXYLfsfOZZ1t8k0OD0+c6ceuWUpx5N6Fu2YUu0Yv9Rj2nvFnl++G0DSmy1+Q8yvkOGA== +"@helium/spl-utils@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.1.4.tgz#214b6076e76d2cd0095758ed3db33f0824048df3" + integrity sha512-QhEhJuOd9P8GbUKx5f9zI1m2zjN9si/IrAlDQk4gkFBDFsi4szzY03rj4CwyhmwIYJk/qi1b4JiMoRIinFutJg== dependencies: - "@coral-xyz/anchor" "^0.26.0" - "@helium/account-fetch-cache" "^0.2.14" - "@helium/address" "^4.10.2" - "@helium/anchor-resolvers" "^0.2.5" - "@metaplex-foundation/mpl-token-metadata" "^2.5.2" + "@coral-xyz/anchor" "0.26.0" + "@helium/address" "^4.8.1" "@solana/spl-account-compression" "^0.1.7" "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.43.4" @@ -2611,16 +2693,11 @@ borsh "^0.7.0" bs58 "^5.0.0" -"@helium/spl-utils@^0.2.6": - version "0.2.6" - resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.6.tgz#20b136f13d9e1d58e7032b4c55b5cc0c1ad9f6dc" - integrity sha512-Ut1bdWRMyru4YOYFB4NS31fbSGYTtW3gMxEg3/c+SKMwAfoZcRIOE8FgOV1nugSrFJ570zsx8R7lHIQYfAOmog== +"@helium/spl-utils@file:.yalc/@helium/spl-utils": + version "0.1.2" dependencies: - "@coral-xyz/anchor" "^0.26.0" - "@helium/account-fetch-cache" "^0.2.5" + "@coral-xyz/anchor" "0.26.0" "@helium/address" "^4.8.1" - "@helium/anchor-resolvers" "^0.2.5" - "@metaplex-foundation/mpl-token-metadata" "^2.5.2" "@solana/spl-account-compression" "^0.1.7" "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.43.4" @@ -2652,6 +2729,32 @@ bn.js "^5.2.0" bs58 "^4.0.1" +"@helium/treasury-management-sdk@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.0.32.tgz#e726d2dff0354c7d1e5c76327f18c3a8a2e5d34b" + integrity sha512-v3at7PDwm8OiMf2fOpsdsSR0FSeOQU7wBb5bwcA5RoZ8vaqWmgi7Mdi8xLwl+dVUyX9EnQkcpisvK4qcwHGfVA== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/circuit-breaker-sdk" "^0.0.32" + "@helium/idls" "^0.0.32" + "@helium/spl-utils" "^0.0.32" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + +"@helium/treasury-management-sdk@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.1.4.tgz#6d3ad274c3d3a7209ebeca6f901f9356e62c973a" + integrity sha512-w7hUTsP+kMMH5f0M/0VqOQ2KzdRACuY5qDHPt4X7VvjgjWFnps/mIHBXV1P2hG2YZDN9CiCSMwwjT9MFHISUiA== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/circuit-breaker-sdk" "^0.1.4" + "@helium/idls" "^0.1.1" + "@helium/spl-utils" "^0.1.4" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + "@helium/treasury-management-sdk@^0.2.14": version "0.2.14" resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.14.tgz#9a640c38f7e7de9e302c8fa3711b683735ca8285" @@ -2677,6 +2780,32 @@ bn.js "^5.2.0" bs58 "^4.0.1" +"@helium/voter-stake-registry-sdk@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.0.32.tgz#e40d39188bc5fbf8c76173172624f30bf68dd806" + integrity sha512-7NyIx1dsYzxAv3/nN4XqVY7LPGDZNtPn6HuPYXu5WUjARrc/MEIK1gpvPyKPDU90MJ580hZwF2WDyVx4r9hPXw== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/idls" "^0.0.32" + "@helium/spl-utils" "^0.0.32" + "@metaplex-foundation/mpl-token-metadata" "^2.2.3" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + +"@helium/voter-stake-registry-sdk@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.1.4.tgz#41d46d1b0364c710aff51df756ed5a2521bf96e7" + integrity sha512-8f+dWaS1IbSuybrvyvchuOd/NP9fCx8jCVyl02pKkURFZC0WdPckiaw+5kh2/y29nwwZJlVqdu7I7C2TR/6uyQ== + dependencies: + "@coral-xyz/anchor" "0.26.0" + "@helium/idls" "^0.1.1" + "@helium/spl-utils" "^0.1.4" + "@metaplex-foundation/mpl-token-metadata" "^2.2.3" + "@solana/spl-token" "^0.3.6" + bn.js "^5.2.0" + bs58 "^4.0.1" + "@helium/voter-stake-registry-sdk@^0.2.14": version "0.2.14" resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.14.tgz#650fe667bf9cec21af66cd8123185a7526fed666" @@ -3246,10 +3375,10 @@ bn.js "^5.2.0" js-sha3 "^0.8.0" -"@metaplex-foundation/mpl-bubblegum@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-bubblegum/-/mpl-bubblegum-0.7.0.tgz#b34067ad4fe846ceb60e47e49f221ecf4730add7" - integrity sha512-HCo6q+nh8M3KRv9/aUaZcJo5/vPJEeZwPGRDWkqN7lUXoMIvhd83fZi7MB1rIg1gwpVHfHqim0A02LCYKisWFg== +"@metaplex-foundation/mpl-bubblegum@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-bubblegum/-/mpl-bubblegum-0.6.2.tgz#e1b098ccef10899b0d759a03e3d4b1ae7bdc9f0c" + integrity sha512-4tF7/FFSNtpozuIGD7gMKcqK2D49eVXZ144xiowC5H1iBeu009/oj2m8Tj6n4DpYFKWJ2JQhhhk0a2q7x0Begw== dependencies: "@metaplex-foundation/beet" "0.7.1" "@metaplex-foundation/beet-solana" "0.4.0" @@ -3258,6 +3387,7 @@ "@solana/spl-account-compression" "^0.1.4" "@solana/spl-token" "^0.1.8" "@solana/web3.js" "^1.50.1" + bn.js "^5.2.0" js-sha3 "^0.8.0" "@metaplex-foundation/mpl-candy-guard@^0.3.0": @@ -3866,6 +3996,18 @@ js-sha3 "^0.8.0" typescript-collections "^1.3.3" +"@solana/spl-account-compression@^0.1.5": + version "0.1.10" + resolved "https://registry.yarnpkg.com/@solana/spl-account-compression/-/spl-account-compression-0.1.10.tgz#b3135ce89349d6090832b3b1d89095badd57e969" + integrity sha512-IQAOJrVOUo6LCgeWW9lHuXo6JDbi4g3/RkQtvY0SyalvSWk9BIkHHe4IkAzaQw8q/BxEVBIjz8e9bNYWIAESNw== + dependencies: + "@metaplex-foundation/beet" "^0.7.1" + "@metaplex-foundation/beet-solana" "^0.4.0" + bn.js "^5.2.1" + borsh "^0.7.0" + js-sha3 "^0.8.0" + typescript-collections "^1.3.3" + "@solana/spl-token@0.3.6": version "0.3.6" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.6.tgz#35473ad2ed71fe91e5754a2ac72901e1b8b26a42" @@ -3896,15 +4038,6 @@ "@solana/buffer-layout-utils" "^0.2.0" buffer "^6.0.3" -"@solana/spl-token@^0.3.8": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf" - integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== - dependencies: - "@solana/buffer-layout" "^4.0.0" - "@solana/buffer-layout-utils" "^0.2.0" - buffer "^6.0.3" - "@solana/wallet-adapter-base@^0.9.17", "@solana/wallet-adapter-base@^0.9.2", "@solana/wallet-adapter-base@^0.9.21", "@solana/wallet-adapter-base@^0.9.22": version "0.9.22" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.22.tgz#97812eaf6aebe01e5fe714326b3c9a0614ae6112" From 814a4166704cb2c4d2f3d804b5ea5fc3f7ca21d4 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Tue, 1 Aug 2023 17:49:05 -0500 Subject: [PATCH 05/23] Things somewhat working --- ios/Podfile.lock | 6 +- package.json | 23 +- src/components/HNTKeyboard.tsx | 6 +- src/components/TokenIcon.tsx | 2 +- .../account/AccountManageTokenListScreen.tsx | 5 +- src/features/account/AccountTokenBalance.tsx | 5 +- src/features/account/TokenListItem.tsx | 8 +- src/features/burn/BurnScreen.tsx | 76 ++- .../collectables/ClaimingRewardsScreen.tsx | 35 +- .../HotspotCompressedListItem.tsx | 4 +- src/features/payment/PaymentError.tsx | 6 +- src/features/payment/PaymentItem.tsx | 9 +- src/features/payment/usePaymentsReducer.ts | 7 +- src/features/swaps/SwapItem.tsx | 9 +- src/features/swaps/SwapScreen.tsx | 14 +- src/hooks/useBN.ts | 9 + src/hooks/useCurrentWallet.ts | 9 + src/hooks/useMetaplexMetadata.ts | 19 + src/hooks/useSimulatedTransaction.ts | 7 +- src/hooks/useSubmitTxn.ts | 8 +- src/storage/TokensProvider.tsx | 2 + src/utils/Balance.tsx | 50 +- src/utils/solanaUtils.ts | 15 +- yarn.lock | 434 ++++++------------ 24 files changed, 297 insertions(+), 471 deletions(-) create mode 100644 src/hooks/useBN.ts create mode 100644 src/hooks/useCurrentWallet.ts diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b761b4cf7..a85de240a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -861,9 +861,9 @@ SPEC CHECKSUMS: FBLazyVector: f1897022b53abf1469d6ad692ee2c69f57d967f3 FBReactNativeSpec: 627fd07f1b9d498c9fa572e76d7f1a6b1ee9a444 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 791fe035093b84822da7f0870421a25839ca7870 + glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b helium-react-native-sdk: 32c0a7e3abc733a7f3d291013b2db31475fc6980 - hermes-engine: 7a53ccac09146018a08239c5425625fdb79a6162 + hermes-engine: 0784cadad14b011580615c496f77e0ae112eed75 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 MapboxCommon: fdf7fd31c90b7b607cd9c63e37797f023c01d860 MapboxCoreMaps: 24270c7c6b8cb71819fc2f3c549db9620ee4d019 @@ -871,7 +871,7 @@ SPEC CHECKSUMS: MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d OneSignalXCFramework: 81ceac017a290f23793443323090cfbe888f74ea - RCT-Folly: 85766c3226c7ec638f05ad7cb3cf6a268d6c4241 + RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: bd6045fbd511da5efe6db89eecb21e4e36bd7cbf RCTTypeSafety: c06d9f906faa69dd1c88223204c3a24767725fd8 React: b9ea33557ef1372af247f95d110fbdea114ed3b2 diff --git a/package.json b/package.json index 4ac7f9ff4..1082df351 100644 --- a/package.json +++ b/package.json @@ -39,25 +39,26 @@ "@coral-xyz/anchor": "0.26.0", "@gorhom/bottom-sheet": "4.4.6", "@gorhom/portal": "1.0.14", - "@helium/account-fetch-cache": "^0.2.14", - "@helium/account-fetch-cache-hooks": "^0.2.14", + "@helium/account-fetch-cache": "^0.2.16", + "@helium/account-fetch-cache-hooks": "^0.2.16", "@helium/address": "4.6.2", + "@helium/circuit-breaker-sdk": "^0.2.16", "@helium/crypto-react-native": "4.8.0", "@helium/currency": "4.11.1", "@helium/currency-utils": "0.1.1", - "@helium/data-credits-sdk": "0.2.14", - "@helium/distributor-oracle": "file:.yalc/@helium/distributor-oracle", - "@helium/fanout-sdk": "0.1.2", - "@helium/helium-entity-manager-sdk": "file:.yalc/@helium/helium-entity-manager-sdk", - "@helium/helium-react-hooks": "file:.yalc/@helium/helium-react-hooks", - "@helium/helium-sub-daos-sdk": "0.2.14", + "@helium/data-credits-sdk": "0.2.16", + "@helium/distributor-oracle": "^0.2.16", + "@helium/fanout-sdk": "^0.2.16", + "@helium/helium-entity-manager-sdk": "^0.2.16", + "@helium/helium-react-hooks": "^0.2.16", + "@helium/helium-sub-daos-sdk": "0.2.16", "@helium/http": "4.7.5", - "@helium/idls": "^0.2.5", - "@helium/lazy-distributor-sdk": "file:.yalc/@helium/lazy-distributor-sdk", + "@helium/idls": "^0.2.16", + "@helium/lazy-distributor-sdk": "^0.2.16", "@helium/onboarding": "4.9.0", "@helium/proto-ble": "4.0.0", "@helium/react-native-sdk": "1.0.0", - "@helium/spl-utils": "file:.yalc/@helium/spl-utils", + "@helium/spl-utils": "^0.2.16", "@helium/transactions": "4.8.1", "@helium/treasury-management-sdk": "0.1.2", "@helium/voter-stake-registry-sdk": "0.1.2", diff --git a/src/components/HNTKeyboard.tsx b/src/components/HNTKeyboard.tsx index 5db8f70b4..846ac6002 100644 --- a/src/components/HNTKeyboard.tsx +++ b/src/components/HNTKeyboard.tsx @@ -7,12 +7,11 @@ import { import { Portal } from '@gorhom/portal' import { useMint, useOwnedAmount } from '@helium/helium-react-hooks' import useBackHandler from '@hooks/useBackHandler' +import { useCurrentWallet } from '@hooks/useCurrentWallet' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' -import { usePublicKey } from '@hooks/usePublicKey' import { BoxProps } from '@shopify/restyle' import { NATIVE_MINT } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' -import { useAccountStorage } from '@storage/AccountStorageProvider' import { Theme } from '@theme/theme' import { useOpacity, useSafeTopPaddingStyle } from '@theme/themeHooks' import BN from 'bn.js' @@ -99,8 +98,7 @@ const HNTKeyboardSelector = forwardRef( const [headerHeight, setHeaderHeight] = useState(0) const containerStyle = useSafeTopPaddingStyle('android') const { handleDismiss, setIsShowing } = useBackHandler(bottomSheetModalRef) - const { currentAccount } = useAccountStorage() - const wallet = usePublicKey(currentAccount?.solanaAddress) + const wallet = useCurrentWallet() const { amount: balanceForMint } = useOwnedAmount(wallet, mint) diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx index 3a8b45e20..e4c461097 100644 --- a/src/components/TokenIcon.tsx +++ b/src/components/TokenIcon.tsx @@ -24,7 +24,7 @@ const TokenIcon = ({ ticker, size = 40, white, img }: Props) => { if (img) { return ( void }) => { const mint = usePublicKey(token) - const { currentAccount } = useAccountStorage() - const wallet = usePublicKey(currentAccount?.solanaAddress) + const wallet = useCurrentWallet() const { amount, decimals } = useOwnedAmount(wallet, mint) const { json, symbol } = useMetaplexMetadata(mint) const balanceToDisplay = useMemo(() => { diff --git a/src/features/account/AccountTokenBalance.tsx b/src/features/account/AccountTokenBalance.tsx index 6835a86ee..0ba6ab27f 100644 --- a/src/features/account/AccountTokenBalance.tsx +++ b/src/features/account/AccountTokenBalance.tsx @@ -3,8 +3,8 @@ import Text from '@components/Text' import TextTransform from '@components/TextTransform' import { useOwnedAmount, useTokenAccount } from '@helium/helium-react-hooks' import { DC_MINT } from '@helium/spl-utils' +import { useCurrentWallet } from '@hooks/useCurrentWallet' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' -import { usePublicKey } from '@hooks/usePublicKey' import { BoxProps } from '@shopify/restyle' import { PublicKey } from '@solana/web3.js' import { useAccountStorage } from '@storage/AccountStorageProvider' @@ -58,8 +58,7 @@ const AccountTokenBalance = ({ showTicker = true, ...boxProps }: Props) => { - const { currentAccount } = useAccountStorage() - const wallet = usePublicKey(currentAccount?.solanaAddress) + const wallet = useCurrentWallet() const { amount: balance, decimals, diff --git a/src/features/account/TokenListItem.tsx b/src/features/account/TokenListItem.tsx index de399e763..8f9babee8 100644 --- a/src/features/account/TokenListItem.tsx +++ b/src/features/account/TokenListItem.tsx @@ -6,12 +6,11 @@ import TokenIcon from '@components/TokenIcon' import TouchableContainer from '@components/TouchableContainer' import { Ticker } from '@helium/currency' import { useOwnedAmount } from '@helium/helium-react-hooks' +import { useCurrentWallet } from '@hooks/useCurrentWallet' import useHaptic from '@hooks/useHaptic' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' -import { usePublicKey } from '@hooks/usePublicKey' import { useNavigation } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' -import { useAccountStorage } from '@storage/AccountStorageProvider' import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { useCallback, useMemo } from 'react' @@ -24,15 +23,12 @@ type Props = { } const TokenListItem = ({ mint }: Props) => { const navigation = useNavigation() - const { currentAccount } = useAccountStorage() - const wallet = usePublicKey(currentAccount?.solanaAddress) + const wallet = useCurrentWallet() const { amount, decimals, loading: loadingOwned, } = useOwnedAmount(wallet, mint) - // const amount = BigInt(0) - // const decimals = 0 const { triggerImpact } = useHaptic() const { json, symbol, loading } = useMetaplexMetadata(mint) const mintStr = mint.toBase58() diff --git a/src/features/burn/BurnScreen.tsx b/src/features/burn/BurnScreen.tsx index e61563a95..47c14fdbf 100644 --- a/src/features/burn/BurnScreen.tsx +++ b/src/features/burn/BurnScreen.tsx @@ -18,10 +18,18 @@ import TokenSelector, { } from '@components/TokenSelector' import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address, { NetTypes } from '@helium/address' -import Balance, { CurrencyType } from '@helium/currency' -import { IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { useOwnedAmount, useSolOwnedAmount } from '@helium/helium-react-hooks' +import { + DC_MINT, + IOT_MINT, + MOBILE_MINT, + humanReadable, +} from '@helium/spl-utils' import { TokenBurnV1 } from '@helium/transactions' import useAlert from '@hooks/useAlert' +import { useBN } from '@hooks/useBN' +import { useCurrentWallet } from '@hooks/useCurrentWallet' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' import { PublicKey } from '@solana/web3.js' import { useColors, useHitSlop } from '@theme/themeHooks' @@ -48,7 +56,7 @@ import useSubmitTxn from '../../hooks/useSubmitTxn' import { useAccountStorage } from '../../storage/AccountStorageProvider' import { CSAccount } from '../../storage/cloudStorage' import { RootState } from '../../store/rootReducer' -import { balanceToString, useBalance } from '../../utils/Balance' +import { useBalance } from '../../utils/Balance' import { accountNetType, ellipsizeAddress, @@ -61,6 +69,8 @@ import PaymentItem from '../payment/PaymentItem' import PaymentSubmit from '../payment/PaymentSubmit' import PaymentSummary from '../payment/PaymentSummary' +const FEE = new BN(TXN_FEE_IN_SOL) + type Route = RouteProp const BurnScreen = () => { const route = useRoute() @@ -80,12 +90,10 @@ const BurnScreen = () => { const accountSelectorRef = useRef(null) const { submitDelegateDataCredits } = useSubmitTxn() const addressBookRef = useRef(null) - const { - networkTokensToDc, - hntBalance, - solBalance, - dcBalance, - } = useBalance() + const { networkTokensToDc } = useBalance() + const wallet = useCurrentWallet() + const solBalance = useBN(useSolOwnedAmount(wallet).amount) + const dcBalance = useBN(useOwnedAmount(wallet, DC_MINT).amount) const { showOKAlert } = useAlert() const hntKeyboardRef = useRef(null) const [dcAmount, setDcAmount] = useState(new BN(route.params.amount)) @@ -96,6 +104,7 @@ const BurnScreen = () => { (reduxState: RootState) => reduxState.solana.delegate, ) const [mint, setMint] = useState(MOBILE_MINT) + const { symbol } = useMetaplexMetadata(mint) const tokenSelectorRef = useRef(null) const { isDelegate } = useMemo(() => route.params, [route.params]) @@ -123,10 +132,6 @@ const BurnScreen = () => { return amount }, [dcAmount, route.params.amount]) - const feeAsTokens = useMemo(() => { - return Balance.fromFloat(TXN_FEE_IN_SOL, CurrencyType.solTokens) - }, []) - const amountInDc = useMemo(() => { if (!amountBalance) return return networkTokensToDc(amountBalance) @@ -178,7 +183,7 @@ const BurnScreen = () => { if (isDelegate && amountBalance) { await submitDelegateDataCredits( delegateAddress, - amountBalance.integerBalance, + amountBalance.toNumber(), mint, ) } @@ -217,30 +222,23 @@ const BurnScreen = () => { ) const insufficientFunds = useMemo(() => { - if (!amountBalance || !feeAsTokens || !dcBalance || !solBalance) - return false - - if (amountBalance.floatBalance > dcBalance.floatBalance) { - return true - } - - if (feeAsTokens.floatBalance > solBalance.floatBalance) { - return true - } + if (!amountBalance || !dcBalance || !solBalance) return false - return hntBalance && hntBalance.floatBalance < feeAsTokens.floatBalance - }, [amountBalance, dcBalance, feeAsTokens, hntBalance, solBalance]) + return amountBalance.gt(dcBalance) || FEE.gt(solBalance) + }, [amountBalance, dcBalance, solBalance]) const errors = useMemo(() => { const errStrings: string[] = [] if (insufficientFunds) { errStrings.push( - t('payment.insufficientFunds', { token: amountBalance?.type.mint }), + t('payment.insufficientFunds', { + token: dcBalance && amountBalance.gt(dcBalance) ? 'DC' : 'SOL', + }), ) } return errStrings - }, [amountBalance, insufficientFunds, t]) + }, [amountBalance, dcBalance, insufficientFunds, t]) const onConfirmBalance = useCallback((opts) => { setDcAmount(new BN(opts.balance)) @@ -293,7 +291,7 @@ const BurnScreen = () => { [networkType, isDelegate], ) - const onMintSelected = useCallback((tick: mint) => { + const onMintSelected = useCallback((tick: PublicKey) => { setMint(tick) }, []) @@ -321,8 +319,8 @@ const BurnScreen = () => { { { }) }} handleAddressError={handleAddressError} - mint={mint} + mint={DC_MINT} address={delegateAddress} amount={amountBalance} hasError={hasError} @@ -472,9 +470,7 @@ const BurnScreen = () => { color="secondaryText" > {t('payment.fee', { - value: balanceToString(feeAsTokens, { - maxDecimalPlaces: 4, - }), + value: humanReadable(FEE, 9), })} @@ -530,14 +526,15 @@ const BurnScreen = () => { backgroundColor="secondary" > { { const { currentAccount } = useAccountStorage() const navigation = useNavigation() - const { solBalance } = useBalance() + const wallet = useCurrentWallet() + const solBalance = useBN(useSolOwnedAmount(wallet).amount) const { bottom } = useSafeAreaInsets() const { cluster, anchorProvider } = useSolana() diff --git a/src/features/collectables/HotspotCompressedListItem.tsx b/src/features/collectables/HotspotCompressedListItem.tsx index 7bdd3bc17..5c2f7eade 100644 --- a/src/features/collectables/HotspotCompressedListItem.tsx +++ b/src/features/collectables/HotspotCompressedListItem.tsx @@ -45,7 +45,7 @@ const HotspotListItem = ({ if (!hotspot.pendingRewards) return const num = toNumber( new BN(hotspot.pendingRewards[Mints.IOT]), - iotMint?.info.decimals || 6, + iotMint?.decimals || 6, ) return formatLargeNumber(new BigNumber(num)) }, [hotspot, iotMint]) @@ -60,7 +60,7 @@ const HotspotListItem = ({ if (!hotspot.pendingRewards) return const num = toNumber( new BN(hotspot.pendingRewards[Mints.MOBILE]), - mobileMint?.info.decimals || 6, + mobileMint?.decimals || 6, ) return formatLargeNumber(new BigNumber(num)) }, [hotspot, mobileMint]) diff --git a/src/features/payment/PaymentError.tsx b/src/features/payment/PaymentError.tsx index 94e5f1efa..cd48720f0 100644 --- a/src/features/payment/PaymentError.tsx +++ b/src/features/payment/PaymentError.tsx @@ -4,12 +4,11 @@ import Box from '@components/Box' import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' import { useOwnedAmount } from '@helium/helium-react-hooks' -import { usePublicKey } from '@hooks/usePublicKey' +import { useCurrentWallet } from '@hooks/useCurrentWallet' import { useNavigation } from '@react-navigation/native' import { SerializedError } from '@reduxjs/toolkit' import { NATIVE_MINT } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' -import { useAccountStorage } from '@storage/AccountStorageProvider' import { parseTransactionError } from '@utils/solanaUtils' import BN from 'bn.js' import React, { memo, useMemo } from 'react' @@ -36,8 +35,7 @@ const PaymentError = ({ }: Props) => { const navigation = useNavigation() const { t } = useTranslation() - const { currentAccount } = useAccountStorage() - const wallet = usePublicKey(currentAccount?.solanaAddress) + const wallet = useCurrentWallet() const { amount: solBalance } = useOwnedAmount(wallet, NATIVE_MINT) const errorMessage = useMemo(() => { diff --git a/src/features/payment/PaymentItem.tsx b/src/features/payment/PaymentItem.tsx index 0d5dc8f54..4465956f3 100644 --- a/src/features/payment/PaymentItem.tsx +++ b/src/features/payment/PaymentItem.tsx @@ -7,7 +7,6 @@ import Text from '@components/Text' import TextInput from '@components/TextInput' import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address from '@helium/address' -import { Balance, DataCredits } from '@helium/currency' import { useMint } from '@helium/helium-react-hooks' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { BoxProps } from '@shopify/restyle' @@ -25,7 +24,7 @@ import { TextInputEndEditingEventData, } from 'react-native' import { CSAccount } from '../../storage/cloudStorage' -import { balanceToString, useBalance } from '../../utils/Balance' +import { useBalance } from '../../utils/Balance' import { accountNetType, ellipsizeAddress } from '../../utils/accountUtils' export type Payment = { @@ -40,7 +39,7 @@ export type Payment = { type Props = { index: number hasError?: boolean - fee?: Balance + fee?: BN onAddressBookSelected: (opts: { address?: string; index: number }) => void onEditAmount: (opts: { address?: string; index: number }) => void onToggleMax?: (opts: { address?: string; index: number }) => void @@ -271,9 +270,7 @@ const PaymentItem = ({ {fee && ( {t('payment.fee', { - value: balanceToString(feeAsTokens, { - maxDecimalPlaces: 4, - }), + value: humanReadable(feeAsTokens, 8), })} )} diff --git a/src/features/payment/usePaymentsReducer.ts b/src/features/payment/usePaymentsReducer.ts index 9738c2727..b0215e187 100644 --- a/src/features/payment/usePaymentsReducer.ts +++ b/src/features/payment/usePaymentsReducer.ts @@ -1,5 +1,4 @@ import { NetTypes } from '@helium/address' -import Balance, { USDollars } from '@helium/currency' import { PublicKey } from '@solana/web3.js' import BN from 'bn.js' import { useReducer } from 'react' @@ -70,7 +69,7 @@ type PaymentState = { totalAmount: BN error?: string mint: PublicKey - oraclePrice?: Balance + oraclePrice?: BN netType: NetTypes.NetType networkFee?: BN balance: BN @@ -80,7 +79,7 @@ const initialState = (opts: { mint: PublicKey payments?: Payment[] netType: NetTypes.NetType - oraclePrice?: Balance + oraclePrice?: BN balance?: BN }): PaymentState => ({ error: undefined, @@ -309,6 +308,6 @@ function reducer( export default (opts: { netType: NetTypes.NetType mint: PublicKey - oraclePrice?: Balance + oraclePrice?: BN balance?: BN }) => useReducer(reducer, initialState(opts)) diff --git a/src/features/swaps/SwapItem.tsx b/src/features/swaps/SwapItem.tsx index 5415e1b94..c78e53bed 100644 --- a/src/features/swaps/SwapItem.tsx +++ b/src/features/swaps/SwapItem.tsx @@ -24,7 +24,7 @@ export type SwapItemProps = { const SwapItem = ({ isPaying, onCurrencySelect, - mintSelected: currencySelected, + mintSelected, amount, loading = false, onPress, @@ -32,7 +32,7 @@ const SwapItem = ({ ...rest }: SwapItemProps) => { const { t } = useTranslation() - const { symbol, json } = useMetaplexMetadata(currencySelected) + const { symbol, json } = useMetaplexMetadata(mintSelected) const { backgroundStyle: generateBackgroundStyle } = useCreateOpacity() @@ -121,10 +121,7 @@ const SwapItem = ({ {/** If last decimals are zeroes do not show */} {!loading ? amount.toString() : t('generic.loading')} - {`${currencySelected}`} + {`${symbol}`} {isPaying && ( diff --git a/src/features/swaps/SwapScreen.tsx b/src/features/swaps/SwapScreen.tsx index 5a4224102..f9a2bac66 100644 --- a/src/features/swaps/SwapScreen.tsx +++ b/src/features/swaps/SwapScreen.tsx @@ -17,7 +17,6 @@ import TextTransform from '@components/TextTransform' import TokenSelector, { TokenSelectorRef } from '@components/TokenSelector' import TouchableOpacityBox from '@components/TouchableOpacityBox' import TreasuryWarningScreen from '@components/TreasuryWarningScreen' -import Balance, { CurrencyType } from '@helium/currency' import { useMint } from '@helium/helium-react-hooks' import { DC_MINT, @@ -298,14 +297,7 @@ const SwapScreen = () => { } if (youPayMint.equals(HNT_MINT) && currentAccount) { - const networkTokens = Balance.fromFloat( - Number(youPayTokenAmount), - CurrencyType.networkToken, - ) - const rawBalance = networkTokensToDc(networkTokens)?.floatBalance - if (typeof rawBalance !== 'undefined') { - return Math.floor(rawBalance) - } + return networkTokensToDc(new BN(youPayTokenAmount))?.toNumber() || 0 } return 0 @@ -329,9 +321,9 @@ const SwapScreen = () => { ? new PublicKey(recipient) : new PublicKey(currentAccount.solanaAddress) - if (youPayMint.equals(HNT_MINT)) { + if (youPayMint.equals(HNT_MINT) && youReceiveTokenAmount) { await submitMintDataCredits({ - dcAmount: youReceiveTokenAmount, + dcAmount: new BN(youReceiveTokenAmount), recipient: recipientAddr, }) } diff --git a/src/hooks/useBN.ts b/src/hooks/useBN.ts new file mode 100644 index 000000000..5833092a3 --- /dev/null +++ b/src/hooks/useBN.ts @@ -0,0 +1,9 @@ +import BN from 'bn.js' +import { useMemo } from 'react' + +export function useBN(num: bigint | undefined): BN | undefined { + return useMemo(() => { + if (typeof num === 'undefined') return undefined + return new BN(num.toString()) + }, [num]) +} diff --git a/src/hooks/useCurrentWallet.ts b/src/hooks/useCurrentWallet.ts new file mode 100644 index 000000000..40ec0bd97 --- /dev/null +++ b/src/hooks/useCurrentWallet.ts @@ -0,0 +1,9 @@ +import { PublicKey } from '@solana/web3.js' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { usePublicKey } from './usePublicKey' + +export function useCurrentWallet(): PublicKey | undefined { + const { currentAccount } = useAccountStorage() + + return usePublicKey(currentAccount?.solanaAddress) +} diff --git a/src/hooks/useMetaplexMetadata.ts b/src/hooks/useMetaplexMetadata.ts index 820d43883..8bc04baad 100644 --- a/src/hooks/useMetaplexMetadata.ts +++ b/src/hooks/useMetaplexMetadata.ts @@ -6,13 +6,16 @@ import { sol, toMetadata, } from '@metaplex-foundation/js' +import { NATIVE_MINT } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' import { useMemo } from 'react' import { useAsync } from 'react-async-hook' const MPL_PID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s') +// eslint-disable-next-line @typescript-eslint/no-explicit-any const cache: Record = {} +// eslint-disable-next-line @typescript-eslint/no-explicit-any async function getMetadata(uri: string | undefined): Promise { if (uri) { if (!cache[uri]) { @@ -27,6 +30,7 @@ async function getMetadata(uri: string | undefined): Promise { export function useMetaplexMetadata(mint: PublicKey | undefined): { loading: boolean metadata: Metadata | undefined + // eslint-disable-next-line @typescript-eslint/no-explicit-any json: any | undefined symbol: string | undefined name: string | undefined @@ -57,6 +61,21 @@ export function useMetaplexMetadata(mint: PublicKey | undefined): { metadataAcc?.uri, ]) + if (mint?.equals(NATIVE_MINT)) { + return { + metadata: undefined, + loading: false, + json: { + name: 'SOL', + symbol: 'SOL', + image: + 'https://github.com/solana-labs/token-list/blob/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png?raw=true', + }, + symbol: 'SOL', + name: 'SOL', + } + } + return { loading: jsonLoading || loading, json, diff --git a/src/hooks/useSimulatedTransaction.ts b/src/hooks/useSimulatedTransaction.ts index 1e9e3a21c..4d95d1ca1 100644 --- a/src/hooks/useSimulatedTransaction.ts +++ b/src/hooks/useSimulatedTransaction.ts @@ -191,11 +191,8 @@ export function useSimulatedTransaction( if (result?.value.err) { console.warn('failed to simulate', result?.value.err) - if ( - JSON.stringify(result?.value.err).includes( - 'InstructionError":[0,{"Custom":1}]', - ) - ) { + console.warn(result?.value.logs?.join('\n')) + if (JSON.stringify(result?.value.err).includes('{"Custom":1}')) { if (!hasEnoughSol) { await showHNTConversionAlert() } diff --git a/src/hooks/useSubmitTxn.ts b/src/hooks/useSubmitTxn.ts index ed2f26984..3bae6ba07 100644 --- a/src/hooks/useSubmitTxn.ts +++ b/src/hooks/useSubmitTxn.ts @@ -357,13 +357,7 @@ export default () => { }, []) const submitMintDataCredits = useCallback( - async ({ - dcAmount, - recipient, - }: { - dcAmount: number - recipient: PublicKey - }) => { + async ({ dcAmount, recipient }: { dcAmount: BN; recipient: PublicKey }) => { if (!currentAccount || !anchorProvider || !walletSignBottomSheetRef) { throw new Error(t('errors.account')) } diff --git a/src/storage/TokensProvider.tsx b/src/storage/TokensProvider.tsx index d1df42d69..da28a47f5 100644 --- a/src/storage/TokensProvider.tsx +++ b/src/storage/TokensProvider.tsx @@ -1,4 +1,5 @@ import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { NATIVE_MINT } from '@solana/spl-token' import { PublicKey } from '@solana/web3.js' import React, { ReactNode, @@ -21,6 +22,7 @@ export const DEFAULT_TOKENS = new Set([ MOBILE_MINT.toBase58(), IOT_MINT.toBase58(), DC_MINT.toBase58(), + NATIVE_MINT.toBase58(), ]) const useVisibleTokensHook = () => { diff --git a/src/utils/Balance.tsx b/src/utils/Balance.tsx index 06ecbe510..b01b5727e 100644 --- a/src/utils/Balance.tsx +++ b/src/utils/Balance.tsx @@ -1,4 +1,3 @@ -import { NetTypes } from '@helium/address' import Balance, { CurrencyType, DataCredits, @@ -12,6 +11,7 @@ import Balance, { import { getOraclePrice } from '@helium/currency-utils' import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' import { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' import { round } from 'lodash' import React, { ReactNode, @@ -37,6 +37,7 @@ import StoreAtaBalance from './StoreAtaBalance' import StoreSolBalance from './StoreSolBalance' import { accountCurrencyType } from './accountUtils' import { decimalSeparator, groupSeparator } from './i18n' +import { humanReadable } from './solanaUtils' import { useBalanceHistory } from './useBalanceHistory' import { usePollTokenPrices } from './usePollTokenPrices' @@ -81,7 +82,7 @@ const useBalanceHook = () => { ) }, [cluster, anchorProvider, allBalances]) - const { result: oraclePrice } = useAsync(async () => { + const { result: hntToDcPrice } = useAsync(async () => { if (!anchorProvider) { return } @@ -90,9 +91,9 @@ const useBalanceHook = () => { cluster, connection: anchorProvider.connection, }) - return Balance.fromFloat( - oraclePriceRaw.emaPrice.value - oraclePriceRaw.emaConfidence.value * 2, - CurrencyType.usd, + return new BN( + oraclePriceRaw.emaPrice.value - + oraclePriceRaw.emaConfidence.value * 2 * 100000, ) }, [cluster, anchorProvider?.connection]) @@ -111,28 +112,19 @@ const useBalanceHook = () => { }, [tokenPrices]) const dcToNetworkTokens = useCallback( - ( - dcBalance: Balance, - ): Balance | undefined => { - if (!oraclePrice) return + (dcBalance: BN): BN | undefined => { + if (!hntToDcPrice) return - if (currentAccount?.netType === NetTypes.TESTNET) { - return dcBalance.toTestNetworkTokens(oraclePrice) - } - return dcBalance.toNetworkTokens(oraclePrice) + return dcBalance.div(hntToDcPrice) }, - [currentAccount, oraclePrice], + [hntToDcPrice], ) - const networkTokensToDc = useCallback( - ( - balance: Balance, - ): Balance | undefined => { - if (!oraclePrice) return - - return balance.toDataCredits(oraclePrice) + (balance: BN): BN | undefined => { + if (!hntToDcPrice) return + balance.mul(hntToDcPrice) }, - [oraclePrice], + [hntToDcPrice], ) const floatToBalance = useCallback( @@ -249,14 +241,8 @@ const useBalanceHook = () => { currency, ) - const dcBalance = new Balance( - getBalance(DC_MINT, atas), - CurrencyType.dataCredit, - ) - const formattedDcValue = await CurrencyFormatter.format( - dcBalance.toUsd(oraclePrice).floatBalance, - 'usd', - ) + const dcBalance = new BN(getBalance(DC_MINT, atas)) + const formattedDcValue = humanReadable(dcBalance, 5) const formattedTotal = await CurrencyFormatter.format( solValue + hntValue + mobileValue + iotValue, @@ -286,7 +272,7 @@ const useBalanceHook = () => { cluster, currency, getBalance, - oraclePrice, + hntToDcPrice, solanaAddress, tokenPrices, ]) @@ -367,7 +353,7 @@ const useBalanceHook = () => { intToBalance, networkTokensToDc, oracleDateTime, - oraclePrice, + oraclePrice: hntToDcPrice, solanaPrice, toCurrencyString, toPreferredCurrencyString, diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 9843531aa..eba4a8234 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -133,10 +133,13 @@ export function humanReadable( /\B(?=(\d{3})+(?!\d))/g, groupSeparator, ) - const decimalPart = input - .slice(-decimals) - .padStart(decimals, '0') // Add prefix zeros - .replace(/0+$/, '') // Remove trailing zeros + const decimalPart = + decimals !== 0 + ? input + .slice(-decimals) + .padStart(decimals, '0') // Add prefix zeros + .replace(/0+$/, '') // Remove trailing zeros + : '' return `${formattedIntegerPart.length > 0 ? formattedIntegerPart : '0'}${ Number(decimalPart) !== 0 ? `${decimalSeparator}${decimalPart}` : '' @@ -612,7 +615,7 @@ export const mintDataCredits = async ({ recipient, }: { anchorProvider: AnchorProvider - dcAmount: number + dcAmount: BN recipient: PublicKey }) => { try { @@ -624,7 +627,7 @@ export const mintDataCredits = async ({ const tx = await program.methods .mintDataCreditsV0({ hntAmount: null, - dcAmount: toBN(dcAmount, 0), + dcAmount, }) .accounts({ dcMint: DC_MINT, diff --git a/yarn.lock b/yarn.lock index 27041af85..a66673e33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2259,19 +2259,19 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@helium/account-fetch-cache-hooks@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache-hooks/-/account-fetch-cache-hooks-0.2.14.tgz#78ead8cb09f39c13067089dd14c9d208c7d344e2" - integrity sha512-jD2YOjmbUyZEznUJ0Z9ojrmvgAP8+nbH9aoFq1a5ENZcSp0+B9XEWw7XQ+KDTrECw51vpSsUrtxFJYj3WjG4PA== +"@helium/account-fetch-cache-hooks@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache-hooks/-/account-fetch-cache-hooks-0.2.16.tgz#b085baa6c959207e3dcee306d2f9cedf9c3db178" + integrity sha512-5gO11VZuVrP22U2Gtdy46V2iSq+sLWWnLVj6mS1iYMxXFb/Fox0hmv3JkRVR3Ax4UXRrd0iWrI1KBy2DaNqU6Q== dependencies: - "@helium/account-fetch-cache" "^0.2.14" + "@helium/account-fetch-cache" "^0.2.16" "@solana/web3.js" "^1.66.2" react-async-hook "^4.0.0" -"@helium/account-fetch-cache@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.14.tgz#9112da0635ffd2b5145d10c85beedc13d65a62b8" - integrity sha512-jPfpe2ZpXP2n7K++JlXEFK8iAT3wMGdNAaB1vGv/vG5twOvW+bgDfD6oW6qFt0hOywnIYIdjQe4cEsYSOYiizA== +"@helium/account-fetch-cache@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.16.tgz#2283f1511279622354a3a6b53b5e573e8a820cfa" + integrity sha512-GEHD1xjYMWTLJOr4TEdrczu/J7LMusEcmh1qc+ZZ8QSHPm7xjNG8ELVTdUiL8f6Sg3EKxezErCUFz/7u2cc1Sg== dependencies: "@solana/web3.js" "^1.43.4" @@ -2284,6 +2284,15 @@ js-sha256 "^0.9.0" multiformats "^9.6.4" +"@helium/address@^4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.10.2.tgz#56960b118fceb6b6ddabe3e4ecec467d9ae50e26" + integrity sha512-qCswC7Z3GXuJyHv36RcOSnffeghjqJQx0fdu2Lxpf9fgOnIi1JZO2tjjk1mBaqOwCyp+0YzrTPUoEukL/WCtsA== + dependencies: + bs58 "^5.0.0" + js-sha256 "^0.9.0" + multiformats "^9.6.4" + "@helium/address@^4.6.2", "@helium/address@^4.8.0", "@helium/address@^4.8.1": version "4.8.1" resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.8.1.tgz#d8d7cefc6aa7791d79eb8759befb821aaccec3ff" @@ -2293,26 +2302,14 @@ js-sha256 "^0.9.0" multiformats "^9.6.4" -"@helium/anchor-resolvers@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/anchor-resolvers/-/anchor-resolvers-0.2.5.tgz#d23fcd319d1f2444d9ebfa2273f9339beb8204bd" - integrity sha512-AOVa93cbFEUSDHR6OWHk5SlwwrnyStAZbjM1AGGUJdZEVzR1YnkYCl8aFZtVBCkCPmFz5iZDBjEx32chhRsEJA== +"@helium/anchor-resolvers@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/anchor-resolvers/-/anchor-resolvers-0.2.16.tgz#4a5deb1799a79ca8525d2a8e5262b4c2b6d3485a" + integrity sha512-mKDuvUJHTDll7Jm7U8VgHnIDmQIsfIqjEXVkQ9mZ8/fOZK3EhyYk9Ws7u4EvwphNuyYugqobyC43XmRaudxRfA== dependencies: "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.43.4" -"@helium/circuit-breaker-sdk@^0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.0.32.tgz#e7fbafdadae1078290b8d3f0b53b81bf7b4628e7" - integrity sha512-dFKFc2UehuTVHKANWXX8GUfkmmKqLQX5aOX5+Ngm5aWkgZSkGZEbjl1G/ke8B6xwH3sm+fe19ID+mtBseuk2mw== - dependencies: - "@coral-xyz/anchor" "^0.26.0" - "@helium/idls" "^0.0.32" - "@helium/spl-utils" "^0.0.32" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - "@helium/circuit-breaker-sdk@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.1.2.tgz#b7f684ba1a5dbc36027a66b85c8914a7ca0e43c7" @@ -2325,26 +2322,14 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/circuit-breaker-sdk@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.1.4.tgz#53a49a70d533540e4118c19f2d04cd63c556b390" - integrity sha512-9Fa1zxhYO9Nh+iZuPgA4dpyAp9Le2TRoVRu/caWWDC8DNC9Ba2Hd/xeWbRDExymryVZqq741U57OiAi3cPXwbQ== - dependencies: - "@coral-xyz/anchor" "^0.26.0" - "@helium/idls" "^0.1.1" - "@helium/spl-utils" "^0.1.4" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/circuit-breaker-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.14.tgz#a325cf881f8dec6b7f398a4a08f8a74e70610d63" - integrity sha512-Y9BLVw4oJNd82oJWYYa3JpsRsnT7FE39j00Q5ZuIuYDcDHZWdOYBxZCZT/y3wEc20P0MQpDC2n7yMwvI3imXmA== +"@helium/circuit-breaker-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.16.tgz#17099fd12473132965ba211c8995403bd7744a43" + integrity sha512-iniL/gVIfaZ16z9NZjpwGGEsRJpyKxkBJmUrfG2inLumoX/A/K3YjHOIdhGTaEJ3V6NNgyyBp2xB5ex8o2blqA== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/idls" "^0.2.5" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/idls" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2381,32 +2366,37 @@ dependencies: bignumber.js "^9.0.0" -"@helium/data-credits-sdk@0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/data-credits-sdk/-/data-credits-sdk-0.2.14.tgz#0ee3984fb672c03a200c8ca43e7f0947e7f890dc" - integrity sha512-pg+jY+EcdDkaOoI0BFbwl4t5HiZkxGN++s0JDU1vP+Vy/qYJ+vx9HwlXGvVZlyE6UV8naJN4+c0C5zStwJdjfQ== +"@helium/data-credits-sdk@0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/data-credits-sdk/-/data-credits-sdk-0.2.16.tgz#53dbc4148118d68ec0eb714fdefb9ef007d47a12" + integrity sha512-Oox6Mt165yw8k9YpT7Xelk3xNbhUaJlGJY2C+uxCCoSrFUa/XEFXZ4UKvOjoYX/Uw+qi/6jEMhwfqXnXU5nJLA== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.14" - "@helium/helium-sub-daos-sdk" "^0.2.14" - "@helium/idls" "^0.2.5" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/circuit-breaker-sdk" "^0.2.16" + "@helium/helium-sub-daos-sdk" "^0.2.16" + "@helium/idls" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" -"@helium/distributor-oracle@file:.yalc/@helium/distributor-oracle": - version "0.1.2" +"@helium/distributor-oracle@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/distributor-oracle/-/distributor-oracle-0.2.16.tgz#7c56cdf57c2e3c1c0825eafd7025fc572f210bc8" + integrity sha512-522MooHz5HqgkWB7Iluy00pqzWK+JLiyAuJdsp3hjMlgHr/pgVKI1sMkwPEgyBneTgC7lt//Z9sADuenHpmgZg== dependencies: "@coral-xyz/anchor" "^0.26.0" "@fastify/cors" "^8.1.1" - "@helium/address" "^4.6.2" - "@helium/helium-entity-manager-sdk" "^0.1.2" - "@helium/helium-sub-daos-sdk" "^0.1.2" - "@helium/idls" "^0.1.1" - "@helium/lazy-distributor-sdk" "^0.1.2" - "@helium/rewards-oracle-sdk" "^0.1.1" - "@helium/spl-utils" "^0.1.2" + "@helium/account-fetch-cache" "^0.2.16" + "@helium/address" "^4.10.2" + "@helium/helium-entity-manager-sdk" "^0.2.16" + "@helium/helium-sub-daos-sdk" "^0.2.16" + "@helium/idls" "^0.2.16" + "@helium/lazy-distributor-sdk" "^0.2.16" + "@helium/rewards-oracle-sdk" "^0.2.16" + "@helium/spl-utils" "^0.2.16" + "@metaplex-foundation/mpl-bubblegum" "^0.7.0" + "@solana/spl-token" "^0.3.8" "@types/sequelize" "^4.28.14" aws-sdk "^2.1313.0" axios "^0.27.2" @@ -2420,101 +2410,57 @@ sequelize "^6.28.0" typescript-collections "^1.3.3" -"@helium/fanout-sdk@0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@helium/fanout-sdk/-/fanout-sdk-0.1.2.tgz#05ed4ec1eb8323e42bb5de69f0e1e9c2ee34f672" - integrity sha512-islOocQkPIpQpW/avWAE6v7N+uJ2AxPqMmmTXIeGUhALPDvrGU7m9a76lo1eDK5Ghakkt9N1LOH+Nd2+EfsXqA== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/idls" "^0.1.1" - "@helium/spl-utils" "^0.1.2" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/helium-entity-manager-sdk@^0.1.2": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.1.5.tgz#96457885d125c781831a1b595b3cb5e3d6a91f1d" - integrity sha512-1zvavQVXDXmH5IFKYJJwGPxV4iMPQYiGJKk6do1jGFFizfGelDD7raUq+csnMiNExYQeOT8vQSyjNpQuYKjU2g== +"@helium/fanout-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/fanout-sdk/-/fanout-sdk-0.2.16.tgz#72beb66a47089a28b68ee849ce932b1a252f7f9d" + integrity sha512-a/81yhGXSsYcEMSaP7MOQDMxoU117J5UUzUj0Zindmn0pXyF2VpahOqZY4KYiJdatbr7LpAuRfwZYdKVuQP8TA== dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/address" "^4.6.2" - "@helium/helium-sub-daos-sdk" "^0.1.4" - "@helium/idls" "^0.1.1" - "@helium/spl-utils" "^0.1.4" - "@metaplex-foundation/mpl-bubblegum" "^0.6.2" - "@metaplex-foundation/mpl-token-metadata" "^2.2.3" - "@solana/spl-account-compression" "^0.1.5" - "@solana/spl-token" "^0.3.6" + "@coral-xyz/anchor" "^0.26.0" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/idls" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" - crypto-js "^4.1.1" - js-sha256 "^0.9.0" -"@helium/helium-entity-manager-sdk@file:.yalc/@helium/helium-entity-manager-sdk": - version "0.0.32" +"@helium/helium-entity-manager-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.2.16.tgz#c4ad10ed7546d34fe0819f63a2837b2bd2ce14b1" + integrity sha512-abS86eXoII2zNQDp8UZZPDkYu2NrwOY+/KMTVceuAVtDqhLslltDYC4ToV/bS1GCETjm+VJLZcgt0lXIeFfuJA== dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/address" "^4.6.2" - "@helium/helium-sub-daos-sdk" "^0.0.32" - "@helium/idls" "^0.0.32" - "@helium/spl-utils" "^0.0.32" - "@metaplex-foundation/mpl-bubblegum" "^0.6.2" - "@metaplex-foundation/mpl-token-metadata" "^2.2.3" - "@solana/spl-account-compression" "^0.1.5" - "@solana/spl-token" "^0.3.6" + "@coral-xyz/anchor" "^0.26.0" + "@helium/address" "^4.10.2" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/helium-sub-daos-sdk" "^0.2.16" + "@helium/idls" "^0.2.16" + "@helium/spl-utils" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" js-sha256 "^0.9.0" -"@helium/helium-react-hooks@file:.yalc/@helium/helium-react-hooks": - version "0.2.14" +"@helium/helium-react-hooks@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/helium-react-hooks/-/helium-react-hooks-0.2.16.tgz#2cda7af118207d7645c81f268956d73e62548db2" + integrity sha512-cQxExKqQdCFgWNj0s6Z/WAYibalVMQT+NN+8Hk3Mwb/HlwZsTSmD2pQbxnx2fUpQi/i2ZfVO1sycgzX97k0jIw== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/account-fetch-cache" "^0.2.14" - "@helium/account-fetch-cache-hooks" "^0.2.14" + "@helium/account-fetch-cache" "^0.2.16" + "@helium/account-fetch-cache-hooks" "^0.2.16" "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.66.2" bs58 "^5.0.0" pako "^2.0.3" react-async-hook "^4.0.0" -"@helium/helium-sub-daos-sdk@0.2.14", "@helium/helium-sub-daos-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.2.14.tgz#8dd88525491a8f0504343d4b88ae1c9f5580abd3" - integrity sha512-TSadUwMVN9jD0aDN5t9n3S2b6X6qR+PVfbwZsCKXRoeEJ8Pi7qqeMkHbpH1dKz2B6UGGQWnLva6zsiIuaZoShw== +"@helium/helium-sub-daos-sdk@0.2.16", "@helium/helium-sub-daos-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.2.16.tgz#4ad6eb45b1bb28773f9695a6fadd337338bb586e" + integrity sha512-goa7XVEkUSCqLT+eihRFrsTzUAjiH9ybWv8HPEvuxB/xbLXp/ACqe4clldqLwT4DWTTr9aCkfc3G7ptSO6lgoQ== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.14" - "@helium/treasury-management-sdk" "^0.2.14" - "@helium/voter-stake-registry-sdk" "^0.2.14" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/helium-sub-daos-sdk@^0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.0.32.tgz#0141a03a67e67fc63fc95e12d2983d5476f2e348" - integrity sha512-1jGpoVSuqJAP+omnRXet0EVJlooP/pulml4x1hbE3XtCFOhU0Ev31RFmoAuuVZmpuKdP8CtUzyEjQpOzO2yqsA== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.0.32" - "@helium/spl-utils" "^0.0.32" - "@helium/treasury-management-sdk" "^0.0.32" - "@helium/voter-stake-registry-sdk" "^0.0.32" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/helium-sub-daos-sdk@^0.1.2", "@helium/helium-sub-daos-sdk@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.1.4.tgz#99852310f0f8fa4e7afa2d128f3d2eff884b65d4" - integrity sha512-O7OiEYrZeLBHJJAdzPuG3JygrZ4i+cb3l5QnyQ+pIVpunuOfsA+fNpzgzDH2MBE9MDUkOr3kR3uSF3Jy3DA9ww== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.4" - "@helium/spl-utils" "^0.1.4" - "@helium/treasury-management-sdk" "^0.1.4" - "@helium/voter-stake-registry-sdk" "^0.1.4" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/circuit-breaker-sdk" "^0.2.16" + "@helium/treasury-management-sdk" "^0.2.16" + "@helium/voter-stake-registry-sdk" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2531,17 +2477,6 @@ retry-axios "^2.1.2" snakecase-keys "^5.1.0" -"@helium/idls@^0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.0.32.tgz#5b5ce1b8fabfbe6494d1fa0b1eb202dd19f4e2c6" - integrity sha512-74s0qqHdwk/X5iWPcGxytOIsrjeLo4xB87koy99/2Tilht085pu0RdEnM9TwTg3Nx7zaRTcr82yt1cjOySoCFg== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@solana/web3.js" "^1.43.4" - bn.js "^5.2.0" - borsh "^0.7.0" - bs58 "^4.0.1" - "@helium/idls@^0.0.43": version "0.0.43" resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.0.43.tgz#de77dccd27411f6f2eed6daabb8b1ea1f600fe19" @@ -2564,10 +2499,10 @@ borsh "^0.7.0" bs58 "^4.0.1" -"@helium/idls@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.2.5.tgz#96f00926027a8f6beb3a51e877cc5a74210dcd2c" - integrity sha512-KzoKeWnfvQJiRDi6eiDADbUTvVyOLre8ht46LeSq02Nx/UQO2SmgymI/YjwlO0o8Zm7L/r8dxym0+MYG++lLHw== +"@helium/idls@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.2.16.tgz#908fbceb1f8a634235f0ec98fd23c6e022a43ab5" + integrity sha512-c1gp1PQ9+m832UpoOTo1SmcbqJ4pcMARQMklkFJx27l0iyHkL1zI4IusyXWH9yoKIgSklnlPKEm67b3LYd1Nsg== dependencies: "@coral-xyz/anchor" "^0.26.0" "@solana/web3.js" "^1.43.4" @@ -2575,27 +2510,14 @@ borsh "^0.7.0" bs58 "^4.0.1" -"@helium/lazy-distributor-sdk@^0.1.2": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.1.4.tgz#f221cb946bb7730c6422c901d56a0588c92f6b10" - integrity sha512-8Ouh1lqmi7Tt6m3Vr3NNrxK/6VJ9qs6w9Aj/PUjhc8oc8uaOCJ4qrwNLwq4fHogsn9XduE/JBKROGCyL9iOFEQ== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.4" - "@helium/spl-utils" "^0.1.4" - "@metaplex-foundation/mpl-token-metadata" "^2.2.3" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/lazy-distributor-sdk@file:.yalc/@helium/lazy-distributor-sdk": - version "0.1.2" +"@helium/lazy-distributor-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.2.16.tgz#c3c3cf589cd16e7e2a3b9a3316b0e25b54c1db79" + integrity sha512-JMrITdyLYGDx52GBPhleDEg13X6p08rCl2GoRjCYmXVhS3rgmnjVtFRVPYyqq1Rh+4O5yGq1h9TO4exyCSguHA== dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.2" - "@helium/spl-utils" "^0.1.2" - "@metaplex-foundation/mpl-token-metadata" "^2.2.3" - "@solana/spl-token" "^0.3.6" + "@coral-xyz/anchor" "^0.26.0" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/circuit-breaker-sdk" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2627,44 +2549,17 @@ resolved "https://registry.yarnpkg.com/@helium/react-native-sdk/-/react-native-sdk-1.0.0.tgz#41024fa99859490bd8a0b717f52acc11ae72f114" integrity sha512-Qi1Nnp/q2hsz2D7aeuM6LxXhNX8NrHz1U+PoQslwK2XfqPFZEYb4uAzjXDKlc+JBWPiF96GMJywv/ofxlZ9XLg== -"@helium/rewards-oracle-sdk@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.1.1.tgz#b056b73e0a0b556b22c3190ece80aa5d52fe62c1" - integrity sha512-Qo5WZ+isTaznv3KNK92V44h7s+AWcD4de/J4pR7Gekii1F9p+0uY/AQAjHyXTUMxBcZu9UxZK19xtUnt5R4K5A== +"@helium/rewards-oracle-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.2.16.tgz#4c0a27b4ac755e041ada9d4cb9ceba4bb1a0b897" + integrity sha512-IdI60FiBYncjYG5FLAuakD/75EN8EZHowBZZ9yBrjxsFe7fc1cShpme+GTK1nQBhXkZlRyU1dcyyWgeICdutgA== dependencies: "@coral-xyz/anchor" "^0.26.0" + "@helium/anchor-resolvers" "^0.2.16" "@helium/idls" "^0.0.43" - "@helium/spl-utils" "^0.0.43" - "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/spl-utils@^0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.0.32.tgz#f665611767b398eb2d6024d9b9e5ddbee40e9191" - integrity sha512-VWp3Ve02X2fOd49Nojfw4yDDFH/Iqi/iZ9AbeTJMpfWSQsixFULVeBGADQQFa4xiIYynI3x+zX5Uo1wzgZuxlw== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/address" "^4.8.1" - "@solana/spl-token" "^0.3.6" - "@solana/web3.js" "^1.43.4" - bn.js "^5.2.0" - borsh "^0.7.0" - bs58 "^5.0.0" - -"@helium/spl-utils@^0.0.43": - version "0.0.43" - resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.0.43.tgz#7b5d7266e5ea56f5fbb3e7635831378f90f90a8a" - integrity sha512-RfETETah5MtKVoZqMmIzeMTgpFdL6bfs1e+7+2U8zcmGPyi8zMNyRDyRWtQZiknkbBtpORfgJEaP3TXwQBaxSQ== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/address" "^4.8.1" - "@solana/spl-token" "^0.3.6" - "@solana/web3.js" "^1.43.4" - bn.js "^5.2.0" - borsh "^0.7.0" - bs58 "^5.0.0" - "@helium/spl-utils@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.1.2.tgz#e12b924bf4bd3217f265250a2720cb7ed2316d1d" @@ -2679,25 +2574,16 @@ borsh "^0.7.0" bs58 "^5.0.0" -"@helium/spl-utils@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.1.4.tgz#214b6076e76d2cd0095758ed3db33f0824048df3" - integrity sha512-QhEhJuOd9P8GbUKx5f9zI1m2zjN9si/IrAlDQk4gkFBDFsi4szzY03rj4CwyhmwIYJk/qi1b4JiMoRIinFutJg== +"@helium/spl-utils@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.16.tgz#3442534dd8121557bf144f1e6fdb74c11361099b" + integrity sha512-pwSqP1XDX4CMOdW/Wycyji2MvnDO9UWoBjFW5vConV/FNeXu0PGb1q8ZegpBRlQVFxxSxkq76jwAAZbHub6X+w== dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/address" "^4.8.1" - "@solana/spl-account-compression" "^0.1.7" - "@solana/spl-token" "^0.3.6" - "@solana/web3.js" "^1.43.4" - bn.js "^5.2.0" - borsh "^0.7.0" - bs58 "^5.0.0" - -"@helium/spl-utils@file:.yalc/@helium/spl-utils": - version "0.1.2" - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/address" "^4.8.1" + "@coral-xyz/anchor" "^0.26.0" + "@helium/account-fetch-cache" "^0.2.16" + "@helium/address" "^4.10.2" + "@helium/anchor-resolvers" "^0.2.16" + "@metaplex-foundation/mpl-token-metadata" "^2.5.2" "@solana/spl-account-compression" "^0.1.7" "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.43.4" @@ -2729,41 +2615,15 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/treasury-management-sdk@^0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.0.32.tgz#e726d2dff0354c7d1e5c76327f18c3a8a2e5d34b" - integrity sha512-v3at7PDwm8OiMf2fOpsdsSR0FSeOQU7wBb5bwcA5RoZ8vaqWmgi7Mdi8xLwl+dVUyX9EnQkcpisvK4qcwHGfVA== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.0.32" - "@helium/idls" "^0.0.32" - "@helium/spl-utils" "^0.0.32" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/treasury-management-sdk@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.1.4.tgz#6d3ad274c3d3a7209ebeca6f901f9356e62c973a" - integrity sha512-w7hUTsP+kMMH5f0M/0VqOQ2KzdRACuY5qDHPt4X7VvjgjWFnps/mIHBXV1P2hG2YZDN9CiCSMwwjT9MFHISUiA== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/circuit-breaker-sdk" "^0.1.4" - "@helium/idls" "^0.1.1" - "@helium/spl-utils" "^0.1.4" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/treasury-management-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.14.tgz#9a640c38f7e7de9e302c8fa3711b683735ca8285" - integrity sha512-KtOF2gkG0qe/81HSnLsJs/oYVRdNGJO7JZPmy9+Rrozt8c+3R0no4KqYRBG1I271ijEwYWpfwiyM8a8i7V6Dsw== +"@helium/treasury-management-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.16.tgz#990963dc04694ebce487586eee398f672b34507b" + integrity sha512-6rm8Xdew6hfkOl6wubsNxMxDX1EXbEC/hQQagEg69WwxJHJwiQSw1Qd0Oh3Si0mqbmuXDb99aTEyD1d1sXGNwg== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/circuit-breaker-sdk" "^0.2.14" - "@helium/idls" "^0.2.5" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/circuit-breaker-sdk" "^0.2.16" + "@helium/idls" "^0.2.16" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2780,40 +2640,14 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/voter-stake-registry-sdk@^0.0.32": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.0.32.tgz#e40d39188bc5fbf8c76173172624f30bf68dd806" - integrity sha512-7NyIx1dsYzxAv3/nN4XqVY7LPGDZNtPn6HuPYXu5WUjARrc/MEIK1gpvPyKPDU90MJ580hZwF2WDyVx4r9hPXw== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/idls" "^0.0.32" - "@helium/spl-utils" "^0.0.32" - "@metaplex-foundation/mpl-token-metadata" "^2.2.3" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/voter-stake-registry-sdk@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.1.4.tgz#41d46d1b0364c710aff51df756ed5a2521bf96e7" - integrity sha512-8f+dWaS1IbSuybrvyvchuOd/NP9fCx8jCVyl02pKkURFZC0WdPckiaw+5kh2/y29nwwZJlVqdu7I7C2TR/6uyQ== - dependencies: - "@coral-xyz/anchor" "0.26.0" - "@helium/idls" "^0.1.1" - "@helium/spl-utils" "^0.1.4" - "@metaplex-foundation/mpl-token-metadata" "^2.2.3" - "@solana/spl-token" "^0.3.6" - bn.js "^5.2.0" - bs58 "^4.0.1" - -"@helium/voter-stake-registry-sdk@^0.2.14": - version "0.2.14" - resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.14.tgz#650fe667bf9cec21af66cd8123185a7526fed666" - integrity sha512-1/YEPkhenaPhSJNTvU792C1j8QQjVH4fvefPg2NzQo9db5LrlYQpTlH+06HONRd5i4bBxKx5y9dL1LPgyDtBrA== +"@helium/voter-stake-registry-sdk@^0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.16.tgz#859f9f726513f286c32ec9416ec98956d56cd9ba" + integrity sha512-QiPVNoixj5XrHZMF0UmyBP83o/T9YZE7EE1RxL9AgjBjcKZjan2OfFb91BwwS377HP9uSFZkrV0ZilAscTep6A== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.5" - "@helium/idls" "^0.2.5" + "@helium/anchor-resolvers" "^0.2.16" + "@helium/idls" "^0.2.16" "@metaplex-foundation/mpl-token-metadata" "^2.2.3" "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" @@ -3375,10 +3209,10 @@ bn.js "^5.2.0" js-sha3 "^0.8.0" -"@metaplex-foundation/mpl-bubblegum@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-bubblegum/-/mpl-bubblegum-0.6.2.tgz#e1b098ccef10899b0d759a03e3d4b1ae7bdc9f0c" - integrity sha512-4tF7/FFSNtpozuIGD7gMKcqK2D49eVXZ144xiowC5H1iBeu009/oj2m8Tj6n4DpYFKWJ2JQhhhk0a2q7x0Begw== +"@metaplex-foundation/mpl-bubblegum@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-bubblegum/-/mpl-bubblegum-0.7.0.tgz#b34067ad4fe846ceb60e47e49f221ecf4730add7" + integrity sha512-HCo6q+nh8M3KRv9/aUaZcJo5/vPJEeZwPGRDWkqN7lUXoMIvhd83fZi7MB1rIg1gwpVHfHqim0A02LCYKisWFg== dependencies: "@metaplex-foundation/beet" "0.7.1" "@metaplex-foundation/beet-solana" "0.4.0" @@ -3387,7 +3221,6 @@ "@solana/spl-account-compression" "^0.1.4" "@solana/spl-token" "^0.1.8" "@solana/web3.js" "^1.50.1" - bn.js "^5.2.0" js-sha3 "^0.8.0" "@metaplex-foundation/mpl-candy-guard@^0.3.0": @@ -3996,18 +3829,6 @@ js-sha3 "^0.8.0" typescript-collections "^1.3.3" -"@solana/spl-account-compression@^0.1.5": - version "0.1.10" - resolved "https://registry.yarnpkg.com/@solana/spl-account-compression/-/spl-account-compression-0.1.10.tgz#b3135ce89349d6090832b3b1d89095badd57e969" - integrity sha512-IQAOJrVOUo6LCgeWW9lHuXo6JDbi4g3/RkQtvY0SyalvSWk9BIkHHe4IkAzaQw8q/BxEVBIjz8e9bNYWIAESNw== - dependencies: - "@metaplex-foundation/beet" "^0.7.1" - "@metaplex-foundation/beet-solana" "^0.4.0" - bn.js "^5.2.1" - borsh "^0.7.0" - js-sha3 "^0.8.0" - typescript-collections "^1.3.3" - "@solana/spl-token@0.3.6": version "0.3.6" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.6.tgz#35473ad2ed71fe91e5754a2ac72901e1b8b26a42" @@ -4038,6 +3859,15 @@ "@solana/buffer-layout-utils" "^0.2.0" buffer "^6.0.3" +"@solana/spl-token@^0.3.8": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf" + integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + buffer "^6.0.3" + "@solana/wallet-adapter-base@^0.9.17", "@solana/wallet-adapter-base@^0.9.2", "@solana/wallet-adapter-base@^0.9.21", "@solana/wallet-adapter-base@^0.9.22": version "0.9.22" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.22.tgz#97812eaf6aebe01e5fe714326b3c9a0614ae6112" From ffef292afab423ce91558cc1a468ecb4f0650211 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Wed, 2 Aug 2023 18:16:17 -0500 Subject: [PATCH 06/23] WIP, tx history broken --- package.json | 6 +- src/components/HNTKeyboard.tsx | 2 +- src/components/TokenSelector.tsx | 8 +- src/features/account/BalancePill.tsx | 76 ----- src/features/account/TransactionDetail.tsx | 39 ++- src/features/account/TxnListItem.tsx | 4 +- src/features/account/useTxn.tsx | 305 +++--------------- .../collectables/TransferCompleteScreen.tsx | 38 +-- src/features/ledger/LedgerAccountListItem.tsx | 19 +- src/features/payment/PaymentScreen.tsx | 12 +- src/features/swaps/SwapItem.tsx | 2 +- src/features/swaps/SwapScreen.tsx | 54 +++- src/features/swaps/SwappingScreen.tsx | 46 +-- src/features/txnDelegation/SignHotspot.tsx | 51 +-- src/features/txnDelegation/useSolTxns.ts | 42 ++- src/hooks/useHntSolConvert.ts | 24 +- src/hooks/useTreasuryManagement.tsx | 9 +- src/hooks/useTreasuryPrice.tsx | 3 +- src/types/activity.ts | 2 - src/utils/Balance.tsx | 6 +- src/utils/solanaUtils.ts | 35 +- yarn.lock | 4 +- 22 files changed, 257 insertions(+), 530 deletions(-) delete mode 100644 src/features/account/BalancePill.tsx diff --git a/package.json b/package.json index 1082df351..2c450200a 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "@coral-xyz/anchor": "0.26.0", "@gorhom/bottom-sheet": "4.4.6", "@gorhom/portal": "1.0.14", - "@helium/account-fetch-cache": "^0.2.16", - "@helium/account-fetch-cache-hooks": "^0.2.16", + "@helium/account-fetch-cache": "file:.yalc/@helium/account-fetch-cache", + "@helium/account-fetch-cache-hooks": "file:.yalc/@helium/account-fetch-cache-hooks", "@helium/address": "4.6.2", "@helium/circuit-breaker-sdk": "^0.2.16", "@helium/crypto-react-native": "4.8.0", @@ -50,7 +50,7 @@ "@helium/distributor-oracle": "^0.2.16", "@helium/fanout-sdk": "^0.2.16", "@helium/helium-entity-manager-sdk": "^0.2.16", - "@helium/helium-react-hooks": "^0.2.16", + "@helium/helium-react-hooks": "file:.yalc/@helium/helium-react-hooks", "@helium/helium-sub-daos-sdk": "0.2.16", "@helium/http": "4.7.5", "@helium/idls": "^0.2.16", diff --git a/src/components/HNTKeyboard.tsx b/src/components/HNTKeyboard.tsx index 846ac6002..6256d7221 100644 --- a/src/components/HNTKeyboard.tsx +++ b/src/components/HNTKeyboard.tsx @@ -186,7 +186,7 @@ const HNTKeyboardSelector = forwardRef( if (!valueAsBalance || !networkFee) return const currentAmount = getNextPayments() - .filter((_v, index) => index !== paymentIndex || 0) // Remove the payment being updated + .filter((_v, index) => index !== (paymentIndex || 0)) // Remove the payment being updated .reduce((prev, current) => { if (!current.amount) { return prev diff --git a/src/components/TokenSelector.tsx b/src/components/TokenSelector.tsx index 12002448a..655732bde 100644 --- a/src/components/TokenSelector.tsx +++ b/src/components/TokenSelector.tsx @@ -5,7 +5,6 @@ import { BottomSheetModal, BottomSheetModalProvider, } from '@gorhom/bottom-sheet' -import { Ticker } from '@helium/currency' import useBackHandler from '@hooks/useBackHandler' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { BoxProps } from '@shopify/restyle' @@ -91,21 +90,22 @@ const TokenSelector = forwardRef( ) const handleTokenPress = useCallback( - (token: string) => () => { + (token: PublicKey) => { bottomSheetModalRef.current?.dismiss() - onTokenSelected(token as Ticker) + onTokenSelected(token) }, [onTokenSelected], ) const keyExtractor = useCallback((item: TokenListItem) => { - return item.mint + return item.mint.toBase58() }, []) const renderFlatlistItem = useCallback( ({ item }: { item: TokenListItem; index: number }) => { return ( handleTokenPress(item.mint)} mint={item.mint} diff --git a/src/features/account/BalancePill.tsx b/src/features/account/BalancePill.tsx deleted file mode 100644 index 3b71dafa3..000000000 --- a/src/features/account/BalancePill.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { NetTypes } from '@helium/address' -import Balance, { - AnyCurrencyType, - DataCredits, - SecurityTokens, - MobileTokens, -} from '@helium/currency' -import React, { memo, useCallback } from 'react' -import DC from '@assets/images/dc.svg' -import MobileIcon from '@assets/images/mobileIcon.svg' -import Helium from '@assets/images/helium.svg' -import Box from '@components/Box' -import Text from '@components/Text' -import { useColors } from '@theme/themeHooks' -import { balanceToString } from '../../utils/Balance' - -type Props = { - netType: NetTypes.NetType - balance?: Balance -} -const BalancePill = ({ netType, balance }: Props) => { - const colors = useColors() - - const getIcon = useCallback(() => { - switch (balance?.type.constructor) { - case DataCredits: - return - case MobileTokens: - return ( - - - - ) - case SecurityTokens: - return ( - - - - ) - default: - return ( - - - - ) - } - }, [balance, colors.blueBright500, colors.purple500]) - - if (!balance || balance.integerBalance <= 0) return null - - return ( - - {getIcon()} - - {balanceToString(balance, { - maxDecimalPlaces: 2, - showTicker: false, - })} - - - ) -} - -export default memo(BalancePill) diff --git a/src/features/account/TransactionDetail.tsx b/src/features/account/TransactionDetail.tsx index 93b9aa2be..8253005a8 100644 --- a/src/features/account/TransactionDetail.tsx +++ b/src/features/account/TransactionDetail.tsx @@ -1,4 +1,16 @@ /* eslint-disable react/no-array-index-key */ +import BlurBox from '@components/BlurBox' +import HandleBasic from '@components/HandleBasic' +import SafeAreaBox from '@components/SafeAreaBox' +import { + BottomSheetBackdrop, + BottomSheetModal, + BottomSheetModalProvider, + BottomSheetScrollView, +} from '@gorhom/bottom-sheet' +import useBackHandler from '@hooks/useBackHandler' +import animalName from 'angry-purple-tiger' +import { groupBy } from 'lodash' import React, { createContext, FC, @@ -9,27 +21,14 @@ import React, { useRef, useState, } from 'react' -import { - BottomSheetBackdrop, - BottomSheetModal, - BottomSheetModalProvider, - BottomSheetScrollView, -} from '@gorhom/bottom-sheet' -import { Edge } from 'react-native-safe-area-context' import { useTranslation } from 'react-i18next' -import { groupBy } from 'lodash' -import animalName from 'angry-purple-tiger' import { LayoutChangeEvent } from 'react-native' -import SafeAreaBox from '@components/SafeAreaBox' -import HandleBasic from '@components/HandleBasic' -import useBackHandler from '@hooks/useBackHandler' -import BlurBox from '@components/BlurBox' -import TransactionLineItem from './TransactionLineItem' -import { useTxnDetails } from './useTxn' -import { useBalance } from '../../utils/Balance' +import { Edge } from 'react-native-safe-area-context' import { useCreateExplorerUrl } from '../../constants/urls' -import { ellipsizeAddress } from '../../utils/accountUtils' import { Activity } from '../../types/activity' +import { ellipsizeAddress } from '../../utils/accountUtils' +import TransactionLineItem from './TransactionLineItem' +import { useTxnDetails } from './useTxn' const initialState = { show: () => undefined, @@ -50,7 +49,6 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { const [contentHeight, setContentHeight] = useState(0) const { handleDismiss, setIsShowing } = useBackHandler(bottomSheetModalRef) - const { intToBalance } = useBalance() const { item: txn } = detailData || {} const { @@ -131,7 +129,6 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { return Object.keys(grouped).map((key) => { const group = grouped[key] const totalAmount = group.reduce((sum, { amount: amt }) => sum + amt, 0) - const balance = intToBalance({ intValue: totalAmount }) const typeName = t(`transactions.rewardTypes.${group[0].type}`) let name = '' if (group[0].gateway) { @@ -141,11 +138,11 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { } return { name, - amount: balance, + amount: totalAmount, type: typeName, } }) - }, [intToBalance, t, txn]) + }, [t, txn]) const handleContentLayout = useCallback((e: LayoutChangeEvent) => { setContentHeight(e.nativeEvent.layout.height) diff --git a/src/features/account/TxnListItem.tsx b/src/features/account/TxnListItem.tsx index ef81dd23b..6cf199dee 100644 --- a/src/features/account/TxnListItem.tsx +++ b/src/features/account/TxnListItem.tsx @@ -1,10 +1,10 @@ -import React, { memo, useCallback, useMemo } from 'react' import Pending from '@assets/images/pending.svg' import Box from '@components/Box' import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' -import useTxn from './useTxn' +import React, { memo, useCallback, useMemo } from 'react' import { Activity } from '../../types/activity' +import useTxn from './useTxn' type Props = { item: Activity diff --git a/src/features/account/useTxn.tsx b/src/features/account/useTxn.tsx index a8245db1f..aa93e8577 100644 --- a/src/features/account/useTxn.tsx +++ b/src/features/account/useTxn.tsx @@ -1,4 +1,11 @@ -import React, { useCallback, useMemo, useState } from 'react' +import TxnReceive from '@assets/images/txnReceive.svg' +import TxnSend from '@assets/images/txnSend.svg' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import { LAMPORTS_PER_SOL } from '@solana/web3.js' +import { Color } from '@theme/theme' +import { useColors } from '@theme/themeHooks' +import animalName from 'angry-purple-tiger' import { addMinutes, format, @@ -6,45 +13,18 @@ import { formatDistanceToNow, fromUnixTime, } from 'date-fns' -import { useTranslation } from 'react-i18next' -import Balance, { - AnyCurrencyType, - CurrencyType, - Ticker, -} from '@helium/currency' import { startCase } from 'lodash' -import TxnReceive from '@assets/images/txnReceive.svg' -import TxnSend from '@assets/images/txnSend.svg' +import React, { useCallback, useMemo, useState } from 'react' import { useAsync } from 'react-async-hook' -import animalName from 'angry-purple-tiger' -import { Color } from '@theme/theme' -import { useColors } from '@theme/themeHooks' -import shortLocale from '../../utils/formatDistance' -import { accountCurrencyType, ellipsizeAddress } from '../../utils/accountUtils' -import { balanceToString, useBalance } from '../../utils/Balance' -import { useOnboarding } from '../onboarding/OnboardingProvider' +import { useTranslation } from 'react-i18next' import { useAccountStorage } from '../../storage/AccountStorageProvider' -import { TXN_FEE_IN_LAMPORTS } from '../../utils/solanaUtils' import { Activity } from '../../types/activity' +import { ellipsizeAddress } from '../../utils/accountUtils' +import shortLocale from '../../utils/formatDistance' +import { TXN_FEE_IN_LAMPORTS } from '../../utils/solanaUtils' +import { useOnboarding } from '../onboarding/OnboardingProvider' -export const TxnTypeKeys = [ - 'rewards_v1', - 'rewards_v2', - 'payment_v1', - 'payment_v2', - 'add_gateway_v1', - 'assert_location_v1', - 'assert_location_v2', - 'transfer_hotspot_v1', - 'transfer_hotspot_v2', - 'token_burn_v1', - 'unstake_validator_v1', - 'stake_validator_v1', - 'transfer_validator_stake_v1', - 'subnetwork_rewards_v1', - 'dc_delegate', - 'dc_mint', -] as const +export const TxnTypeKeys = ['payment_v2', 'dc_delegate', 'dc_mint'] as const type TxnType = typeof TxnTypeKeys[number] const useTxn = ( @@ -53,34 +33,16 @@ const useTxn = ( ) => { const { currentNetworkAddress: address } = useAccountStorage() const colors = useColors() - const { bonesToBalance } = useBalance() const { t } = useTranslation() const { makers } = useOnboarding() - - const ticker = useMemo(() => { - // Get the ticker from the item if it's available - if (item?.payments?.length) { - const firstPaymentTokenType = item.payments[0].tokenType - if (firstPaymentTokenType) { - return accountCurrencyType(address, firstPaymentTokenType).ticker - } - } - return accountCurrencyType(address, undefined).ticker - }, [address, item]) - - const dcBalance = (v: number | undefined | null) => - new Balance(v || 0, CurrencyType.dataCredit) + const { symbol: ticker } = useMetaplexMetadata( + usePublicKey(item?.payments?.[0]?.mint || undefined), + ) const isSending = useMemo(() => { return item?.payer === address }, [address, item]) - const isSelling = useMemo(() => { - if (item?.seller) return item?.seller === address // for transfer_v1 - if (item?.owner) return item?.owner === address // transfer_v2 - return false - }, [address, item]) - const isHotspotTxn = useMemo( () => item?.type === 'assert_location_v1' || @@ -111,28 +73,12 @@ const useTxn = ( const color = useMemo((): Color => { switch (item?.type as TxnType) { - case 'transfer_hotspot_v1': - case 'transfer_hotspot_v2': - return 'orange500' - case 'payment_v1': case 'payment_v2': return isSending ? 'blueBright500' : 'greenBright500' - case 'add_gateway_v1': - case 'assert_location_v1': - case 'assert_location_v2': - return 'greenBright500' - case 'subnetwork_rewards_v1': - case 'rewards_v1': - case 'rewards_v2': - case 'stake_validator_v1': - case 'transfer_validator_stake_v1': case 'dc_mint': return 'greenBright500' - case 'token_burn_v1': case 'dc_delegate': return 'orange500' - case 'unstake_validator_v1': - return 'greenBright500' default: return 'primaryText' } @@ -145,21 +91,17 @@ const useTxn = ( if (item?.pending) { switch (item.type as TxnType) { - case 'payment_v1': case 'payment_v2': if (!isSending) return '' return t('transactions.pending.sending') } } switch (item?.type as TxnType) { - case 'add_gateway_v1': - return t('transactions.added') - case 'payment_v1': case 'payment_v2': { if (item?.payments?.length) { - const firstPaymentTokenType = item.payments[0].tokenType + const firstPaymentTokenType = item.payments[0].mint const hasMixedTokenTypes = item.payments.find( - (p) => p.tokenType !== firstPaymentTokenType, + (p) => p.mint !== firstPaymentTokenType, ) if (hasMixedTokenTypes) { return isSending @@ -171,65 +113,23 @@ const useTxn = ( ? t('transactions.sent', { ticker }) : t('transactions.received', { ticker }) } - case 'assert_location_v1': - return t('transactions.location') - case 'assert_location_v2': - return t('transactions.location_v2') - case 'transfer_hotspot_v1': - case 'transfer_hotspot_v2': - return isSelling - ? t('transactions.transferSell') - : t('transactions.transferBuy') - case 'rewards_v1': - case 'rewards_v2': - return t('transactions.mining') - case 'token_burn_v1': - return t('transactions.burnHNT', { ticker }) - case 'stake_validator_v1': - return t('transactions.stakeValidator', { ticker }) - case 'unstake_validator_v1': - return t('transactions.unstakeValidator', { ticker }) - case 'transfer_validator_stake_v1': - return t('transactions.transferValidator') - case 'subnetwork_rewards_v1': - return item?.tokenType === 'IOT' - ? t('transactions.iotRewards') - : t('transactions.mobileRewards') case 'dc_delegate': return t('transactions.delegated') case 'dc_mint': return t('transactions.received', { ticker: '' }) } - }, [item, t, isSending, ticker, isSelling]) + }, [item, t, isSending, ticker]) const listIcon = useMemo(() => { const iconColor = colors[color] switch (item?.type as TxnType) { - case 'stake_validator_v1': - return - case 'unstake_validator_v1': - return - case 'transfer_validator_stake_v1': - return - case 'payment_v1': case 'payment_v2': return isSending ? ( ) : ( ) - case 'assert_location_v1': - case 'assert_location_v2': - return - case 'rewards_v1': - case 'rewards_v2': - return - case 'token_burn_v1': case 'dc_delegate': - return - case 'transfer_hotspot_v1': - case 'transfer_hotspot_v2': - case 'add_gateway_v1': case 'dc_mint': default: return @@ -239,43 +139,24 @@ const useTxn = ( const isFee = useMemo(() => { // // TODO: Determine if TransferStakeV1 is a fee const type = item?.type as TxnType - if (type === 'payment_v1' || type === 'payment_v2') { + if (type === 'payment_v2') { return isSending } - if ( - type === 'rewards_v1' || - type === 'rewards_v2' || - type === 'unstake_validator_v1' - ) { - return false - } - - if (type === 'transfer_hotspot_v1' || type === 'transfer_hotspot_v2') { - return isSelling - } - return true - }, [isSelling, isSending, item]) + }, [isSending, item]) const formatAmount = useCallback( - (prefix: '-' | '+' | '', amount?: Balance) => { + (prefix: '-' | '+' | '', amount?: number) => { if (!amount) return '' - if (amount?.floatBalance === 0) { - return balanceToString(amount) - } - - return `${prefix}${balanceToString(amount, { maxDecimalPlaces: 4 })}` + return `${prefix}${amount.toFixed(4)}` }, [], ) const getFee = useCallback(async () => { - return formatAmount( - '-', - new Balance(TXN_FEE_IN_LAMPORTS, CurrencyType.solTokens), - ) + return formatAmount('-', TXN_FEE_IN_LAMPORTS / LAMPORTS_PER_SOL) }, [formatAmount]) const getFeePayer = useCallback(() => { @@ -296,115 +177,36 @@ const useTxn = ( }, [item, makers]) const getAmountTitle = useCallback(async () => { - const feePayer = await getFeePayer() if (!item) return '' switch (item.type as TxnType) { - case 'transfer_hotspot_v1': - return t('transactions.amountToSeller') - case 'assert_location_v1': - case 'assert_location_v2': - case 'add_gateway_v1': - return t('transactions.feePaidBy', { feePayer }) - case 'stake_validator_v1': - return t('transactions.stake') - case 'transfer_validator_stake_v1': - case 'unstake_validator_v1': - return t('transactions.stakeAmount') - case 'token_burn_v1': - case 'subnetwork_rewards_v1': - return t('transactions.amount') - case 'payment_v1': - case 'payment_v2': - case 'rewards_v1': - case 'rewards_v2': { - return t('transactions.totalAmount') - } default: return '' } - }, [getFeePayer, item, t]) + }, [item]) const getAmount = useCallback(() => { if (!item) return '' switch (item.type as TxnType) { - case 'rewards_v1': - case 'rewards_v2': { - const rewardsAmount = - item.rewards?.reduce( - (sum, current) => sum.plus(bonesToBalance(current.amount, 'HNT')), - bonesToBalance(0, 'HNT'), - ) || bonesToBalance(0, 'HNT') - return formatAmount('+', rewardsAmount) - } - case 'subnetwork_rewards_v1': { - const { tokenType } = item - const tick = (tokenType?.toUpperCase() || 'MOBILE') as Ticker - const rewardsAmount = - item.rewards?.reduce((sum, current) => { - if (current.account !== address) return sum - return sum.plus(bonesToBalance(current.amount, tick)) - }, bonesToBalance(0, tick)) || bonesToBalance(0, tick) - return formatAmount('+', rewardsAmount) - } - case 'transfer_hotspot_v1': - return formatAmount( - isSelling ? '+' : '-', - bonesToBalance(item.amountToSeller, 'HNT'), - ) - case 'assert_location_v1': - case 'assert_location_v2': - case 'add_gateway_v1': - return formatAmount('-', dcBalance(item.stakingFee)) - case 'stake_validator_v1': - return formatAmount('-', bonesToBalance(item.stake, 'HNT')) - case 'unstake_validator_v1': - return formatAmount('-', bonesToBalance(item.stakeAmount, 'HNT')) - case 'transfer_validator_stake_v1': - return formatAmount( - item.payer === address ? '-' : '+', - bonesToBalance(item.stakeAmount, 'HNT'), - ) - case 'token_burn_v1': - return formatAmount('-', bonesToBalance(item.amount, 'HNT')) - case 'payment_v1': - return formatAmount('', bonesToBalance(item.amount, 'HNT')) case 'dc_delegate': - return formatAmount( - '-', - new Balance(Number(item.amount), CurrencyType.dataCredit), - ) + return formatAmount('-', Number(item.amount)) case 'dc_mint': - return formatAmount( - '+', - new Balance(Number(item.amount), CurrencyType.dataCredit), - ) + return formatAmount('+', Number(item.amount)) case 'payment_v2': { - if (item.payer === address) { - const paymentTotals = item.payments?.reduce( - (sums, current) => { - const tokenType = (current.tokenType?.toUpperCase() || - 'HNT') as Ticker - return { - ...sums, - [tokenType]: sums[tokenType].plus( - bonesToBalance(current.amount, tokenType), - ), - } - }, - { - HNT: bonesToBalance(0, 'HNT'), - IOT: bonesToBalance(0, 'IOT'), - MOBILE: bonesToBalance(0, 'MOBILE'), - } as Record>, - ) + const paymentTotals = item.payments?.reduce((sums, current) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const mint = (current.mint || item.payments?.[0].mint)! + return { + ...sums, + [mint]: (sums[mint] || 0) + current.amount, + } + }, {} as Record) if (!paymentTotals) return '' return Object.keys(paymentTotals) - .flatMap((p) => { - const tick = p.toUpperCase() as Ticker - const total = paymentTotals[tick] - if (total.integerBalance === 0) return [] - const amt = formatAmount('', paymentTotals[tick]) + .flatMap((m) => { + const total = paymentTotals[m] + if (total === 0) return [] + const amt = formatAmount('', paymentTotals[m]) return [amt] }) .join(', ') @@ -412,18 +214,13 @@ const useTxn = ( return `+${item.payments ?.filter((p) => p.payee === address) - .map((p) => - formatAmount( - '', - bonesToBalance(p.amount, p.tokenType?.toUpperCase() as Ticker), - ), - ) + .map((p) => formatAmount('', p.amount)) .join(', ')}` } } return '' - }, [item, formatAmount, isSelling, bonesToBalance, address]) + }, [item, formatAmount, address]) const time = useMemo(() => { if (!item) return '' @@ -457,31 +254,25 @@ const useTxn = ( const payments = item?.payments?.filter(({ payee }) => payee === address) if (!payments) return [] const all = payments.map(async (p) => { - const balance = await formatAmount( - '+', - bonesToBalance(p.amount, p.tokenType?.toUpperCase() as Ticker), - ) + const balance = await formatAmount('+', p.amount) return { amount: balance, payee: p.payee, memo: p.memo || '' } }) return Promise.all(all) - }, [address, formatAmount, item, bonesToBalance]) + }, [address, formatAmount, item]) const getPaymentsSent = useCallback(async () => { if (item?.payer !== address || !item?.payments) { return [] } const all = item.payments.map( - async ({ amount: amt, payee, memo: paymentMemo, tokenType }) => { - const balance = await formatAmount( - '', - bonesToBalance(amt, tokenType?.toUpperCase() as Ticker), - ) + async ({ amount: amt, payee, memo: paymentMemo }) => { + const balance = await formatAmount('', amt) return { amount: balance, payee, memo: paymentMemo || '' } }, ) return Promise.all(all) - }, [address, formatAmount, item, bonesToBalance]) + }, [address, formatAmount, item]) return { time, diff --git a/src/features/collectables/TransferCompleteScreen.tsx b/src/features/collectables/TransferCompleteScreen.tsx index 3411ea2d7..ca5afe8ac 100644 --- a/src/features/collectables/TransferCompleteScreen.tsx +++ b/src/features/collectables/TransferCompleteScreen.tsx @@ -1,28 +1,30 @@ -import React, { useCallback, useMemo, memo } from 'react' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import { LogBox } from 'react-native' -import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' -import { Edge } from 'react-native-safe-area-context' -import 'text-encoding-polyfill' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import BackArrow from '@assets/images/backArrow.svg' -import IndeterminateProgressBar from '@components/IndeterminateProgressBar' -import { DelayedFadeIn } from '@components/FadeInOut' +import { ReAnimatedBox } from '@components/AnimatedBox' +import BackScreen from '@components/BackScreen' import Box from '@components/Box' -import ImageBox from '@components/ImageBox' import ButtonPressable from '@components/ButtonPressable' +import { DelayedFadeIn } from '@components/FadeInOut' +import ImageBox from '@components/ImageBox' +import IndeterminateProgressBar from '@components/IndeterminateProgressBar' import Text from '@components/Text' -import BackScreen from '@components/BackScreen' -import { ReAnimatedBox } from '@components/AnimatedBox' +import { useSolOwnedAmount } from '@helium/helium-react-hooks' +import { useBN } from '@hooks/useBN' +import { useCurrentWallet } from '@hooks/useCurrentWallet' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' import { useSpacing } from '@theme/themeHooks' import { parseTransactionError } from '@utils/solanaUtils' -import { useBalance } from '@utils/Balance' -import { ww } from '../../utils/layout' -import { RootState } from '../../store/rootReducer' -import { CollectableStackParamList } from './collectablesTypes' +import React, { memo, useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { LogBox } from 'react-native' +import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' +import { Edge } from 'react-native-safe-area-context' +import { useSelector } from 'react-redux' +import 'text-encoding-polyfill' import { TabBarNavigationProp } from '../../navigation/rootTypes' +import { RootState } from '../../store/rootReducer' import { Collectable, CompressedNFT } from '../../types/solana' +import { ww } from '../../utils/layout' +import { CollectableStackParamList } from './collectablesTypes' LogBox.ignoreLogs([ 'Non-serializable values were found in the navigation state', @@ -35,7 +37,7 @@ const TransferCollectableScreen = () => { const navigation = useNavigation() const COLLECTABLE_HEIGHT = ww const backEdges = useMemo(() => ['top'] as Edge[], []) - const { solBalance } = useBalance() + const solBalance = useBN(useSolOwnedAmount(useCurrentWallet()).amount) const { t } = useTranslation() const { collectable } = route.params diff --git a/src/features/ledger/LedgerAccountListItem.tsx b/src/features/ledger/LedgerAccountListItem.tsx index e9b78a8ae..4ff883e84 100644 --- a/src/features/ledger/LedgerAccountListItem.tsx +++ b/src/features/ledger/LedgerAccountListItem.tsx @@ -1,12 +1,12 @@ -import React, { useCallback, memo, useMemo } from 'react' -import CheckBox from '@react-native-community/checkbox' +import AccountIcon from '@components/AccountIcon' import Box from '@components/Box' -import Text from '@components/Text' import Surface from '@components/Surface' -import AccountIcon from '@components/AccountIcon' -import { useColors } from '@theme/themeHooks' +import Text from '@components/Text' import { LedgerAccount } from '@hooks/useLedger' -import { balanceToString, useBalance } from '../../utils/Balance' +import CheckBox from '@react-native-community/checkbox' +import { useColors } from '@theme/themeHooks' +import { humanReadable } from '@utils/solanaUtils' +import React, { memo, useCallback, useMemo } from 'react' import { ellipsizeAddress, isTestnet } from '../../utils/accountUtils' export enum Section { @@ -32,11 +32,10 @@ const LedgerAccountListItem = ({ onCheckboxToggled, section, }: LedgerAccountListItemProps) => { - const { bonesToBalance } = useBalance() const colors = useColors() // TODO: Add other token types once nano app supports them - const balance = bonesToBalance(account.balance, 'HNT') + const balance = toBN(account.balance, 8) const disabled = section.index === Section.ALREADY_LINKED const borderTopEndRadius = useMemo( @@ -96,9 +95,7 @@ const LedgerAccountListItem = ({ > {`${ellipsizeAddress(account.address, { numChars: 4, - })} | ${balanceToString(balance, { - maxDecimalPlaces: 2, - })}`} + })} | ${humanReadable(balance, 8)}`} diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 6e22edcc6..591f87b77 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -20,7 +20,7 @@ import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address, { NetTypes } from '@helium/address' import { Ticker } from '@helium/currency' import { useMint, useOwnedAmount } from '@helium/helium-react-hooks' -import { HNT_MINT } from '@helium/spl-utils' +import { DC_MINT, HNT_MINT } from '@helium/spl-utils' import useDisappear from '@hooks/useDisappear' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' @@ -613,10 +613,12 @@ const PaymentScreen = () => { }, [balance, decimals]) const data = useMemo((): TokenListItem[] => { - const tokens = [...visibleTokens].map((token) => ({ - mint: new PublicKey(token), - selected: mint.toBase58() === token, - })) + const tokens = [...visibleTokens] + .filter((vt: string) => vt !== DC_MINT.toBase58()) + .map((token) => ({ + mint: new PublicKey(token), + selected: mint.toBase58() === token, + })) return tokens }, [mint, visibleTokens]) diff --git a/src/features/swaps/SwapItem.tsx b/src/features/swaps/SwapItem.tsx index c78e53bed..7f7410834 100644 --- a/src/features/swaps/SwapItem.tsx +++ b/src/features/swaps/SwapItem.tsx @@ -82,7 +82,7 @@ const SwapItem = ({ alignItems="center" borderRadius="round" > - + { const colors = useColors() const [youPayTokenAmount, setYouPayTokenAmount] = useState(0) const [youReceiveMint, setYouReceiveMint] = useState(HNT_MINT) - const [solFee, setSolFee] = useState(undefined) + const [solFee, setSolFee] = useState(undefined) const [hasInsufficientBalance, setHasInsufficientBalance] = useState< undefined | boolean >() const [networkError, setNetworkError] = useState() const hntKeyboardRef = useRef(null) - const { networkTokensToDc, hntBalance, solBalance } = useBalance() + const wallet = useCurrentWallet() + const solBalance = useBN(useSolOwnedAmount(wallet).amount) + const hntBalance = useBN(useOwnedAmount(wallet, HNT_MINT).amount) + const { networkTokensToDc } = useBalance() const tokenSelectorRef = useRef(null) const { price, @@ -110,7 +125,7 @@ const SwapScreen = () => { const insufficientTokensToSwap = useMemo(() => { if ( youPayMint.equals(HNT_MINT) && - (hntBalance?.floatBalance || 0) < 0.00000001 + (hntBalance || new BN(0)).lt(new BN(1)) ) { return true } @@ -161,19 +176,19 @@ const SwapScreen = () => { ) return - let fee = TXN_FEE_IN_SOL + let fee = new BN(TXN_FEE_IN_LAMPORTS) const ataFee = await getAtaAccountCreationFee({ solanaAddress: currentAccount.solanaAddress, connection, mint: youReceiveMint, }) - fee += ataFee.toNumber() + fee = fee.add(ataFee) setSolFee(fee) setHasInsufficientBalance( - fee > solBalance.integerBalance || solBalance.floatBalance < 0.000005, + fee.gt(solBalance || new BN(0)) || solBalance?.lt(new BN(5000)), ) }, [ anchorProvider, @@ -296,12 +311,27 @@ const SwapScreen = () => { return price } - if (youPayMint.equals(HNT_MINT) && currentAccount) { - return networkTokensToDc(new BN(youPayTokenAmount))?.toNumber() || 0 + if ( + youPayMint.equals(HNT_MINT) && + currentAccount && + typeof decimals !== 'undefined' && + typeof youPayTokenAmount !== 'undefined' + ) { + return toNumber( + networkTokensToDc(toBN(youPayTokenAmount, decimals)) || new BN(0), + decimals, + ) } return 0 - }, [currentAccount, networkTokensToDc, price, youPayTokenAmount, youPayMint]) + }, [ + currentAccount, + networkTokensToDc, + price, + youPayTokenAmount, + youPayMint, + decimals, + ]) const handleSwapTokens = useCallback(async () => { if (connection) { @@ -507,7 +537,7 @@ const SwapScreen = () => { variant="body3Medium" color="white" i18nKey="collectablesScreen.transferFee" - values={{ amount: solFee }} + values={{ amount: humanReadable(solFee, 9) }} /> ) : ( diff --git a/src/features/swaps/SwappingScreen.tsx b/src/features/swaps/SwappingScreen.tsx index a42614f88..714f01582 100644 --- a/src/features/swaps/SwappingScreen.tsx +++ b/src/features/swaps/SwappingScreen.tsx @@ -1,25 +1,29 @@ -import React, { memo, useCallback, useMemo } from 'react' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' -import { Edge } from 'react-native-safe-area-context' -import 'text-encoding-polyfill' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import IndeterminateProgressBar from '@components/IndeterminateProgressBar' -import { DelayedFadeIn } from '@components/FadeInOut' +import { ReAnimatedBox } from '@components/AnimatedBox' +import BackScreen from '@components/BackScreen' import Box from '@components/Box' import ButtonPressable from '@components/ButtonPressable' +import { DelayedFadeIn } from '@components/FadeInOut' +import IndeterminateProgressBar from '@components/IndeterminateProgressBar' import Text from '@components/Text' -import BackScreen from '@components/BackScreen' -import { ReAnimatedBox } from '@components/AnimatedBox' import TokenIcon from '@components/TokenIcon' +import { useSolOwnedAmount } from '@helium/helium-react-hooks' +import { useBN } from '@hooks/useBN' +import { useCurrentWallet } from '@hooks/useCurrentWallet' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { usePublicKey } from '@hooks/usePublicKey' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' import { parseTransactionError } from '@utils/solanaUtils' -import { useBalance } from '@utils/Balance' -import { RootState } from '../../store/rootReducer' -import BackArrow from '../../assets/images/backArrow.svg' -import { SwapStackParamList } from './swapTypes' +import React, { memo, useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' +import { Edge } from 'react-native-safe-area-context' +import { useSelector } from 'react-redux' +import 'text-encoding-polyfill' import ArrowRight from '../../assets/images/arrowRight.svg' +import BackArrow from '../../assets/images/backArrow.svg' import { TabBarNavigationProp } from '../../navigation/rootTypes' +import { RootState } from '../../store/rootReducer' +import { SwapStackParamList } from './swapTypes' type Route = RouteProp @@ -27,12 +31,12 @@ const SwappingScreen = () => { const route = useRoute() const navigation = useNavigation() const backEdges = useMemo(() => ['bottom'] as Edge[], []) - const { solBalance } = useBalance() + const solBalance = useBN(useSolOwnedAmount(useCurrentWallet()).amount) const { t } = useTranslation() const { tokenA, tokenB } = route.params - const { jsonA } = useMetaplexMetadata(usePublicKey(tokenA)) - const { jsonB } = useMetaplexMetadata(usePublicKey(tokenB)) + const { json: jsonA } = useMetaplexMetadata(usePublicKey(tokenA)) + const { json: jsonB } = useMetaplexMetadata(usePublicKey(tokenB)) const solanaPayment = useSelector( (reduxState: RootState) => reduxState.solana.payment, @@ -55,7 +59,7 @@ const SwappingScreen = () => { padding="s" marginEnd="m" > - + { borderRadius="round" padding="s" > - + ) - }, [jsonA?.img, jsonB?.img]) + }, [jsonA?.image, jsonB?.image]) return ( @@ -312,14 +313,14 @@ const SignHotspot = () => { )} - {((solana.burnAmounts?.hntFee?.integerBalance || 0) > 0 || - (solana.burnAmounts?.dcFee?.integerBalance || 0) > 0) && ( + {(solana.burnAmounts?.hntFee?.gt(new BN(0)) || + solana.burnAmounts?.dcFee?.gt(new BN(0))) && ( <> {t('signHotspot.burnAmounts')} - {(solana.burnAmounts?.dcFee?.integerBalance || 0) > 0 && ( + {solana.burnAmounts?.dcFee?.gt(new BN(0)) && ( { )} - {(solana.burnAmounts?.hntFee?.integerBalance || 0) > 0 && ( + {solana.burnAmounts?.hntFee?.gt(new BN(0)) && ( | null - hntFee?: Balance | null + dcFee?: BN | null + hntFee?: BN | null } const useSolTxns = (heliumAddress: string, solanaTransactions?: string) => { @@ -268,17 +262,17 @@ const useSolTxns = (heliumAddress: string, solanaTransactions?: string) => { args: { dcAmount: string | null; hntAmount: string | null } } - let dcFee = 0 - let hntFee = 0 + let dcFee = new BN(0) + let hntFee = new BN(0) if (data.args.dcAmount) { - dcFee = new BN(data.args.dcAmount).toNumber() + dcFee = new BN(data.args.dcAmount) } if (data.args.hntAmount) { - hntFee = new BN(data.args.hntAmount).toNumber() + hntFee = new BN(data.args.hntAmount) } return { - hntFee: new Balance(hntFee, CurrencyType.networkToken), - dcFee: new Balance(dcFee, CurrencyType.dataCredit), + hntFee, + dcFee, name: decodedInstruction.name, } }, diff --git a/src/hooks/useHntSolConvert.ts b/src/hooks/useHntSolConvert.ts index 6f7765b7f..a352c0a82 100644 --- a/src/hooks/useHntSolConvert.ts +++ b/src/hooks/useHntSolConvert.ts @@ -1,17 +1,21 @@ -import { Config } from 'react-native-config' -import { useAsync } from 'react-async-hook' +import { useOwnedAmount, useSolOwnedAmount } from '@helium/helium-react-hooks' +import { HNT_MINT } from '@helium/spl-utils' +import { LAMPORTS_PER_SOL, Transaction } from '@solana/web3.js' import axios from 'axios' -import { Transaction } from '@solana/web3.js' -import { useMemo } from 'react' -import { useBalance } from '@utils/Balance' -import { toNumber } from '@helium/spl-utils' import BN from 'bn.js' +import { useMemo } from 'react' +import { useAsync } from 'react-async-hook' +import { Config } from 'react-native-config' import { useSolana } from '../solana/SolanaProvider' import * as logger from '../utils/logger' +import { useBN } from './useBN' +import { useCurrentWallet } from './useCurrentWallet' export function useHntSolConvert() { const { cluster, anchorProvider } = useSolana() - const { hntBalance, solBalance } = useBalance() + const wallet = useCurrentWallet() + const solBalance = useBN(useSolOwnedAmount(wallet).amount) + const hntBalance = useBN(useOwnedAmount(wallet, HNT_MINT).amount) const baseUrl = useMemo(() => { let url = Config.HNT_TO_RENT_SERVICE_DEVNET_URL @@ -29,7 +33,7 @@ export function useHntSolConvert() { } = useAsync(async () => { try { const { estimate } = (await axios.get(`${baseUrl}/estimate`)).data - return toNumber(new BN(estimate), 8) + return new BN(estimate) } catch (e) { logger.error(e) return 0 @@ -39,9 +43,9 @@ export function useHntSolConvert() { const hasEnoughSol = useMemo(() => { if (!hntBalance || !solBalance || !hntEstimate) return true - if (hntBalance.floatBalance < hntEstimate) return true + if (hntBalance.lt(hntEstimate)) return true - return solBalance.floatBalance > 0.02 + return solBalance.gt(new BN(0.02 * LAMPORTS_PER_SOL)) }, [hntBalance, solBalance, hntEstimate]) const { diff --git a/src/hooks/useTreasuryManagement.tsx b/src/hooks/useTreasuryManagement.tsx index 93e9c0d56..973bcaf1d 100644 --- a/src/hooks/useTreasuryManagement.tsx +++ b/src/hooks/useTreasuryManagement.tsx @@ -1,8 +1,8 @@ -import { IDL } from '@helium/idls/lib/esm/treasury_management' -import { TreasuryManagement as TreasuryManagementType } from '@helium/idls/lib/types/treasury_management' import { IdlAccounts } from '@coral-xyz/anchor' +import { UseAccountState } from '@helium/account-fetch-cache-hooks' +import { useAnchorAccount } from '@helium/helium-react-hooks' +import { TreasuryManagement as TreasuryManagementType } from '@helium/idls/lib/types/treasury_management' import { PublicKey } from '@solana/web3.js' -import { UseAccountState, useIdlAccount } from '@helium/helium-react-hooks' export type TreasuryManagement = IdlAccounts['treasuryManagementV0'] & { @@ -14,9 +14,8 @@ export function useTreasuryManagement( ): UseAccountState { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return useIdlAccount( + return useAnchorAccount( key, - IDL as TreasuryManagementType, type, ) } diff --git a/src/hooks/useTreasuryPrice.tsx b/src/hooks/useTreasuryPrice.tsx index 4bf3cce85..91983472a 100644 --- a/src/hooks/useTreasuryPrice.tsx +++ b/src/hooks/useTreasuryPrice.tsx @@ -44,8 +44,7 @@ export function useTreasuryPrice( // only works for basic exponential curves // dR = (R / S^(1 + k)) ((S + dS)^(1 + k) - S^(1 + k)) const S = Number( - (fromMintAcc as any).info.supply / - BigInt(Math.pow(10, (fromMintAcc as any).info.decimals)), + fromMintAcc.supply / BigInt(Math.pow(10, fromMintAcc.decimals)), ) const R = amountAsNum(r, rDecimals) diff --git a/src/types/activity.ts b/src/types/activity.ts index a9ed41efa..8b11bd691 100644 --- a/src/types/activity.ts +++ b/src/types/activity.ts @@ -21,8 +21,6 @@ export type Activity = { oldAddress?: null | string oldOwner?: null | string owner?: null | string - payee?: null | string - payer?: null | string payments?: null | Array pending?: null | boolean rewards?: null | Array diff --git a/src/utils/Balance.tsx b/src/utils/Balance.tsx index b01b5727e..8f26a2e85 100644 --- a/src/utils/Balance.tsx +++ b/src/utils/Balance.tsx @@ -92,8 +92,8 @@ const useBalanceHook = () => { connection: anchorProvider.connection, }) return new BN( - oraclePriceRaw.emaPrice.value - - oraclePriceRaw.emaConfidence.value * 2 * 100000, + (oraclePriceRaw.emaPrice.value - oraclePriceRaw.emaConfidence.value * 2) * + 100000, ) }, [cluster, anchorProvider?.connection]) @@ -122,7 +122,7 @@ const useBalanceHook = () => { const networkTokensToDc = useCallback( (balance: BN): BN | undefined => { if (!hntToDcPrice) return - balance.mul(hntToDcPrice) + return balance.mul(hntToDcPrice) }, [hntToDcPrice], ) diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index eba4a8234..dbb390041 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -1446,7 +1446,7 @@ export const solInstructionsToActivity = ( const activity: Activity = { hash: signature, type: 'unknown' } - const { transaction, slot, blockTime, meta } = parsedTxn + const { slot, blockTime, meta } = parsedTxn activity.fee = meta?.fee activity.height = slot @@ -1454,42 +1454,29 @@ export const solInstructionsToActivity = ( if (blockTime) { activity.time = blockTime } - if (meta?.preTokenBalances && meta.postTokenBalances) { const { preTokenBalances, postTokenBalances } = meta + let payments = [] as Payment[] postTokenBalances.forEach((post) => { const preBalance = preTokenBalances.find( ({ accountIndex }) => accountIndex === post.accountIndex, ) - const pre = preBalance || { uiTokenAmount: { amount: '0' } } - const preAmount = parseInt(pre.uiTokenAmount.amount, 10) - const postAmount = parseInt(post.uiTokenAmount.amount, 10) + const pre = preBalance || { uiTokenAmount: { uiAmount: 0 } } + const preAmount = pre.uiTokenAmount.uiAmount || 0 + const postAmount = post.uiTokenAmount.uiAmount || 0 const amount = postAmount - preAmount - if (amount < 0) { - // is payer - activity.payer = post.owner - activity.mint = post.mint - activity.amount = -1 * amount - } else { - // is payee - const p: Payment = { - amount, - payee: post.owner || '', - mint: post.mint, - } - payments = [...payments, p] + const p: Payment = { + amount, + payee: '', + mint: post.mint, } + payments = [...payments, p] }) activity.payments = payments } - const transfer = transaction.message.instructions.find((i) => { - const instruction = i as ParsedInstruction - return instruction?.parsed?.type === 'transferChecked' - }) as ParsedInstruction - - if (transfer) { + if ((activity.payments?.length || 0) > 0) { // We have a payment activity.type = 'payment_v2' } diff --git a/yarn.lock b/yarn.lock index a66673e33..213648d2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2437,10 +2437,8 @@ crypto-js "^4.1.1" js-sha256 "^0.9.0" -"@helium/helium-react-hooks@^0.2.16": +"@helium/helium-react-hooks@file:.yalc/@helium/helium-react-hooks": version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/helium-react-hooks/-/helium-react-hooks-0.2.16.tgz#2cda7af118207d7645c81f268956d73e62548db2" - integrity sha512-cQxExKqQdCFgWNj0s6Z/WAYibalVMQT+NN+8Hk3Mwb/HlwZsTSmD2pQbxnx2fUpQi/i2ZfVO1sycgzX97k0jIw== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/account-fetch-cache" "^0.2.16" From e73e07a3a415645ce04fb2f13fa37bd382f745eb Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 3 Aug 2023 11:17:19 -0500 Subject: [PATCH 07/23] Fix activity list --- src/features/account/AccountTokenScreen.tsx | 12 +- src/features/account/TransactionDetail.tsx | 164 +----------- src/features/account/TxnListItem.tsx | 8 +- .../account/useSolanaActivityList.tsx | 21 +- src/features/account/useTxn.tsx | 240 ++++++++---------- src/hooks/useMetaplexMetadata.ts | 46 ++-- src/locales/en.ts | 3 +- src/types/activity.ts | 32 +-- src/utils/solanaUtils.ts | 30 +-- 9 files changed, 183 insertions(+), 373 deletions(-) diff --git a/src/features/account/AccountTokenScreen.tsx b/src/features/account/AccountTokenScreen.tsx index d3fd7c4f5..3d7bd8dca 100644 --- a/src/features/account/AccountTokenScreen.tsx +++ b/src/features/account/AccountTokenScreen.tsx @@ -177,9 +177,10 @@ const AccountTokenScreen = () => { showTxnDetail({ item, accountAddress: currentAccount?.address || '', + mint, }) }, - [currentAccount, showTxnDetail], + [currentAccount, showTxnDetail, mint], ) const hasAirdrop = useMemo(() => { @@ -250,6 +251,7 @@ const AccountTokenScreen = () => { }} > { ) }, - [activityData, bottomScreenHeaderHeight, now, showTransactionDetail], + [ + activityData?.length, + bottomScreenHeaderHeight, + mint, + now, + showTransactionDetail, + ], ) const renderFooter = useCallback(() => { diff --git a/src/features/account/TransactionDetail.tsx b/src/features/account/TransactionDetail.tsx index 8253005a8..5e8d7c046 100644 --- a/src/features/account/TransactionDetail.tsx +++ b/src/features/account/TransactionDetail.tsx @@ -9,12 +9,11 @@ import { BottomSheetScrollView, } from '@gorhom/bottom-sheet' import useBackHandler from '@hooks/useBackHandler' -import animalName from 'angry-purple-tiger' -import { groupBy } from 'lodash' +import { PublicKey } from '@solana/web3.js' import React, { - createContext, FC, ReactNode, + createContext, useCallback, useContext, useMemo, @@ -26,7 +25,6 @@ import { LayoutChangeEvent } from 'react-native' import { Edge } from 'react-native-safe-area-context' import { useCreateExplorerUrl } from '../../constants/urls' import { Activity } from '../../types/activity' -import { ellipsizeAddress } from '../../utils/accountUtils' import TransactionLineItem from './TransactionLineItem' import { useTxnDetails } from './useTxn' @@ -34,7 +32,7 @@ const initialState = { show: () => undefined, } -type DetailData = { item: Activity; accountAddress: string } +type DetailData = { item: Activity; accountAddress: string; mint: PublicKey } type TransactionDetailSelectorActions = { show: (data: DetailData) => void } @@ -49,7 +47,7 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { const [contentHeight, setContentHeight] = useState(0) const { handleDismiss, setIsShowing } = useBackHandler(bottomSheetModalRef) - const { item: txn } = detailData || {} + const { item: txn, mint } = detailData || {} const { amount, @@ -57,14 +55,12 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { color, fee, feePayer, - hotspotName, icon, paymentsReceived, paymentsSent, time, title, - validatorName, - } = useTxnDetails(txn) + } = useTxnDetails(mint, txn) const createExplorerUrl = useCreateExplorerUrl() const snapPoints = useMemo(() => { @@ -115,35 +111,6 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { const handleComponent = useCallback(() => , []) - const rewards = useMemo(() => { - if (!txn?.rewards?.length || txn.type === 'subnetwork_rewards_v1') { - return null - } - - const grouped = groupBy(txn.rewards, (reward) => { - if (reward.type === 'securities') return reward.type - - return `${reward.gateway}.${reward.type}` - }) - - return Object.keys(grouped).map((key) => { - const group = grouped[key] - const totalAmount = group.reduce((sum, { amount: amt }) => sum + amt, 0) - const typeName = t(`transactions.rewardTypes.${group[0].type}`) - let name = '' - if (group[0].gateway) { - name = animalName(group[0].gateway) - } else { - name = typeName - } - return { - name, - amount: totalAmount, - type: typeName, - } - }) - }, [t, txn]) - const handleContentLayout = useCallback((e: LayoutChangeEvent) => { setContentHeight(e.nativeEvent.layout.height) }, []) @@ -172,62 +139,14 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { icon={icon} /> - {!!hotspotName && ( - - )} - {!!validatorName && ( - - )} - - {!!txn?.buyer && ( - - )} - - {!!txn?.seller && ( - - )} - - {!!txn?.payee && ( - - )} - - {paymentsSent.map(({ amount: amt, payee }, index) => ( + {paymentsSent.map(({ amount: amt }, index) => ( - ))} @@ -237,33 +156,11 @@ const TransactionDetailSelector = ({ children }: { children: ReactNode }) => { - ))} - {rewards?.map((reward, index) => { - return ( - - ) - })} - {!!amountTitle && ( { /> )} - {!!txn?.owner && ( - - )} - - {!!txn?.oldOwner && ( - - )} - - {!!txn?.oldAddress && ( - - )} - - {!!txn?.newOwner && ( - - )} - - {!!txn?.newAddress && ( - - )} - { export const useTransactionDetail = () => useContext(TransactionDetailSelectorContext) -export const withTransactionDetail = (Component: FC) => () => - ( +export const withTransactionDetail = (Component: FC) => () => { + return ( ) +} diff --git a/src/features/account/TxnListItem.tsx b/src/features/account/TxnListItem.tsx index 6cf199dee..fab5acc52 100644 --- a/src/features/account/TxnListItem.tsx +++ b/src/features/account/TxnListItem.tsx @@ -2,18 +2,22 @@ import Pending from '@assets/images/pending.svg' import Box from '@components/Box' import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' +import { PublicKey } from '@solana/web3.js' import React, { memo, useCallback, useMemo } from 'react' import { Activity } from '../../types/activity' import useTxn from './useTxn' type Props = { + mint: PublicKey item: Activity now: Date isLast: boolean onPress: (item: Activity) => void } -const TxnListItem = ({ item, now, isLast, onPress }: Props) => { - const { listIcon, title, color, time, getAmount } = useTxn(item, { now }) +const TxnListItem = ({ mint, item, now, isLast, onPress }: Props) => { + const { listIcon, title, color, time, getAmount } = useTxn(mint, item, { + now, + }) const amt = useMemo(() => getAmount(), [getAmount]) const handlePress = useCallback(() => { diff --git a/src/features/account/useSolanaActivityList.tsx b/src/features/account/useSolanaActivityList.tsx index 87d17fcb4..5c74b53ba 100644 --- a/src/features/account/useSolanaActivityList.tsx +++ b/src/features/account/useSolanaActivityList.tsx @@ -94,20 +94,21 @@ export default ({ if (filter !== 'in' && filter !== 'out' && filter !== 'all') return [] if (filter === 'in' || filter === 'out') { - const payments = solanaActivity.data[account.solanaAddress]?.payment[ - mintStr - ]?.filter((txn) => txn.mint === mintStr) + const payments = + solanaActivity.data[account.solanaAddress]?.payment[mintStr] return payments?.filter((txn) => - filter === 'out' - ? txn.payee === account.solanaAddress - : txn.payee !== account.solanaAddress, + txn.payments?.some((payment) => + payment.mint === mintStr && + payment.owner === account?.solanaAddress && + filter === 'out' + ? payment.amount < 0 + : payment.amount > 0, + ), ) } - return solanaActivity.data[account.solanaAddress][filter][mintStr]?.filter( - (txn) => txn.mint === mintStr, - ) - }, [account, filter, solanaActivity.data, mintStr]) + return solanaActivity.data[account.solanaAddress][filter][mintStr] + }, [account?.solanaAddress, solanaActivity.data, mintStr, filter]) const loading = useMemo(() => { return solanaActivity.loading diff --git a/src/features/account/useTxn.tsx b/src/features/account/useTxn.tsx index aa93e8577..46684810d 100644 --- a/src/features/account/useTxn.tsx +++ b/src/features/account/useTxn.tsx @@ -1,11 +1,19 @@ import TxnReceive from '@assets/images/txnReceive.svg' import TxnSend from '@assets/images/txnSend.svg' -import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { useAccounts } from '@helium/account-fetch-cache-hooks' +import { MintParser } from '@helium/helium-react-hooks' +import { truthy } from '@helium/spl-utils' +import { useCurrentWallet } from '@hooks/useCurrentWallet' +import { + METADATA_PARSER, + getMetadataId, + useMetaplexMetadata, +} from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' -import { LAMPORTS_PER_SOL } from '@solana/web3.js' +import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' import { Color } from '@theme/theme' import { useColors } from '@theme/themeHooks' -import animalName from 'angry-purple-tiger' +import BN from 'bn.js' import { addMinutes, format, @@ -17,68 +25,68 @@ import { startCase } from 'lodash' import React, { useCallback, useMemo, useState } from 'react' import { useAsync } from 'react-async-hook' import { useTranslation } from 'react-i18next' -import { useAccountStorage } from '../../storage/AccountStorageProvider' import { Activity } from '../../types/activity' -import { ellipsizeAddress } from '../../utils/accountUtils' import shortLocale from '../../utils/formatDistance' -import { TXN_FEE_IN_LAMPORTS } from '../../utils/solanaUtils' -import { useOnboarding } from '../onboarding/OnboardingProvider' +import { TXN_FEE_IN_LAMPORTS, humanReadable } from '../../utils/solanaUtils' -export const TxnTypeKeys = ['payment_v2', 'dc_delegate', 'dc_mint'] as const +export const TxnTypeKeys = ['payment_v2'] as const type TxnType = typeof TxnTypeKeys[number] const useTxn = ( + mint?: PublicKey, item?: Activity, dateOpts?: { dateFormat?: string; now?: Date }, ) => { - const { currentNetworkAddress: address } = useAccountStorage() const colors = useColors() const { t } = useTranslation() - const { makers } = useOnboarding() const { symbol: ticker } = useMetaplexMetadata( usePublicKey(item?.payments?.[0]?.mint || undefined), ) - - const isSending = useMemo(() => { - return item?.payer === address - }, [address, item]) - - const isHotspotTxn = useMemo( + const wallet = useCurrentWallet() + const mintKeys = useMemo( () => - item?.type === 'assert_location_v1' || - item?.type === 'assert_location_v2' || - item?.type === 'add_gateway_v1' || - item?.type === 'transfer_hotspot_v1' || - item?.type === 'transfer_hotspot_v2', - [item], + [...new Set(item?.payments?.map((p) => p.mint))] + .filter(truthy) + .map((k) => new PublicKey(k)), + [item?.payments], ) - - const isValidatorTxn = useMemo( - () => - item?.type === 'stake_validator_v1' || - item?.type === 'transfer_validator_stake_v1' || - item?.type === 'unstake_validator_v1', - [item], + const metadataKeys = useMemo( + () => mintKeys.map((m) => getMetadataId(m)), + [mintKeys], ) + const { accounts: mintAccs } = useAccounts(mintKeys, MintParser) + const { accounts: metadataAccs } = useAccounts(metadataKeys, METADATA_PARSER) + const decimalsByMint = useMemo(() => { + return mintAccs?.reduce((acc, curr) => { + if (curr.info) { + acc[curr.publicKey.toBase58()] = curr.info.decimals + } + return acc + }, {} as { [key: string]: number }) + }, [mintAccs]) + + const symbolsByMint = useMemo(() => { + return metadataAccs?.reduce((acc, curr, index) => { + if (curr.info) { + acc[mintKeys[index].toBase58()] = curr.info.symbol + } + return acc + }, {} as { [key: string]: string }) + }, [metadataAccs, mintKeys]) - const getHotspotName = useCallback(() => { - if (!isHotspotTxn || !item?.gateway) return '' - return animalName(item.gateway) - }, [isHotspotTxn, item]) - - const getValidatorName = useCallback(() => { - if (!isValidatorTxn || !item?.address) return '' - return animalName(item.address) - }, [isValidatorTxn, item]) + const isSending = useMemo(() => { + return item?.payments?.some( + (p) => + p.owner === wallet?.toBase58() && + p.amount < 0 && + p.mint === mint?.toBase58(), + ) + }, [item?.payments, mint, wallet]) const color = useMemo((): Color => { switch (item?.type as TxnType) { case 'payment_v2': return isSending ? 'blueBright500' : 'greenBright500' - case 'dc_mint': - return 'greenBright500' - case 'dc_delegate': - return 'orange500' default: return 'primaryText' } @@ -113,10 +121,6 @@ const useTxn = ( ? t('transactions.sent', { ticker }) : t('transactions.received', { ticker }) } - case 'dc_delegate': - return t('transactions.delegated') - case 'dc_mint': - return t('transactions.received', { ticker: '' }) } }, [item, t, isSending, ticker]) @@ -129,8 +133,6 @@ const useTxn = ( ) : ( ) - case 'dc_delegate': - case 'dc_mint': default: return } @@ -147,34 +149,30 @@ const useTxn = ( }, [isSending, item]) const formatAmount = useCallback( - (prefix: '-' | '+' | '', amount?: number) => { - if (!amount) return '' - - return `${prefix}${amount.toFixed(4)}` + ( + prefix: '-' | '+' | '', + amount: number | undefined, + m: string | undefined | null, + ) => { + const decimals = m ? decimalsByMint?.[m] : undefined + if (!amount || typeof decimals === 'undefined') return '' + const symbolPart = m ? symbolsByMint?.[m] || '' : '' + + return `${prefix}${humanReadable( + new BN( + Math.abs(amount) + .toFixed(decimals || 0) + .replace('.', ''), + ), + decimals, + )} ${symbolPart}` }, - [], + [decimalsByMint, symbolsByMint], ) const getFee = useCallback(async () => { - return formatAmount('-', TXN_FEE_IN_LAMPORTS / LAMPORTS_PER_SOL) - }, [formatAmount]) - - const getFeePayer = useCallback(() => { - const type = item?.type - if ( - !item?.type || - !item.payer || - (type !== 'add_gateway_v1' && - type !== 'assert_location_v1' && - type !== 'assert_location_v2') - ) { - return '' - } - return ( - makers.find(({ address: makerAddress }) => makerAddress === item.payer) - ?.name || ellipsizeAddress(item.payer) - ) - }, [item, makers]) + return `-${TXN_FEE_IN_LAMPORTS / LAMPORTS_PER_SOL}` + }, []) const getAmountTitle = useCallback(async () => { if (!item) return '' @@ -188,39 +186,22 @@ const useTxn = ( if (!item) return '' switch (item.type as TxnType) { - case 'dc_delegate': - return formatAmount('-', Number(item.amount)) - case 'dc_mint': - return formatAmount('+', Number(item.amount)) case 'payment_v2': { - const paymentTotals = item.payments?.reduce((sums, current) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const mint = (current.mint || item.payments?.[0].mint)! - return { - ...sums, - [mint]: (sums[mint] || 0) + current.amount, - } - }, {} as Record) - if (!paymentTotals) return '' - return Object.keys(paymentTotals) - .flatMap((m) => { - const total = paymentTotals[m] - if (total === 0) return [] - const amt = formatAmount('', paymentTotals[m]) - return [amt] - }) - .join(', ') + const payment = item.payments?.find( + (p) => p.mint === mint?.toBase58() && p.owner === wallet?.toBase58(), + ) + if (payment) { + return formatAmount( + payment.amount < 0 ? '-' : '+', + Math.abs(payment.amount), + payment.mint, + ) } - - return `+${item.payments - ?.filter((p) => p.payee === address) - .map((p) => formatAmount('', p.amount)) - .join(', ')}` } } return '' - }, [item, formatAmount, address]) + }, [item, formatAmount, mint, wallet]) const time = useMemo(() => { if (!item) return '' @@ -251,28 +232,30 @@ const useTxn = ( }, [dateOpts, item, t]) const getPaymentsReceived = useCallback(async () => { - const payments = item?.payments?.filter(({ payee }) => payee === address) + const payments = item?.payments?.filter( + ({ owner, amount }) => owner === wallet?.toBase58() && amount > 0, + ) if (!payments) return [] const all = payments.map(async (p) => { - const balance = await formatAmount('+', p.amount) - return { amount: balance, payee: p.payee, memo: p.memo || '' } + const balance = await formatAmount('+', p.amount, p.mint) + return { amount: balance, owner: p.owner, memo: p.memo || '' } }) return Promise.all(all) - }, [address, formatAmount, item]) + }, [formatAmount, item?.payments, wallet]) const getPaymentsSent = useCallback(async () => { - if (item?.payer !== address || !item?.payments) { + if (!item?.payments) { return [] } - const all = item.payments.map( - async ({ amount: amt, payee, memo: paymentMemo }) => { - const balance = await formatAmount('', amt) - return { amount: balance, payee, memo: paymentMemo || '' } - }, - ) + const all = item.payments + .filter((p) => p.amount < 0 && p.owner === wallet?.toBase58()) + .map(async ({ amount: amt, owner, memo: paymentMemo, mint: m }) => { + const balance = await formatAmount('', amt, m) + return { amount: balance, owner, memo: paymentMemo || '' } + }) return Promise.all(all) - }, [address, formatAmount, item]) + }, [formatAmount, item, wallet]) return { time, @@ -282,20 +265,15 @@ const useTxn = ( title, color, isFee, - getFeePayer, getPaymentsReceived, getPaymentsSent, - isHotspotTxn, - isValidatorTxn, - getHotspotName, - getValidatorName, getAmountTitle, } } type Payment = { amount: string - payee: string + owner: string memo: string } type TxnDetails = { @@ -309,28 +287,19 @@ type TxnDetails = { paymentsSent: Payment[] amount: string amountTitle: string - hotspotName: string - validatorName: string - isValidatorTxn: boolean - isHotspotTxn: boolean } -export const useTxnDetails = (item?: Activity) => { +export const useTxnDetails = (mint?: PublicKey, item?: Activity) => { const { listIcon, title, time, color, - getFeePayer, getFee, getPaymentsReceived, getPaymentsSent, getAmount, - getHotspotName, - getValidatorName, - isHotspotTxn, - isValidatorTxn, getAmountTitle, - } = useTxn(item, { + } = useTxn(mint, item, { dateFormat: 'dd MMMM yyyy HH:MM', }) @@ -344,24 +313,18 @@ export const useTxnDetails = (item?: Activity) => { paymentsSent: [], amount: '', amountTitle: '', - validatorName: '', - hotspotName: '', - isHotspotTxn: false, - isValidatorTxn: false, }) useAsync(async () => { - const feePayer = await getFeePayer() const fee = await getFee() const paymentsReceived = await getPaymentsReceived() const paymentsSent = await getPaymentsSent() const amount = await getAmount() const amountTitle = await getAmountTitle() - const validatorName = await getValidatorName() - const hotspotName = await getHotspotName() setDetails({ - feePayer, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain + feePayer: item?.feePayer!, icon: listIcon, title, time, @@ -371,16 +334,11 @@ export const useTxnDetails = (item?: Activity) => { paymentsSent, amount, amountTitle, - validatorName, - hotspotName, - isHotspotTxn, - isValidatorTxn, }) }, [ color, getAmount, getFee, - getFeePayer, getPaymentsReceived, getPaymentsSent, listIcon, diff --git a/src/hooks/useMetaplexMetadata.ts b/src/hooks/useMetaplexMetadata.ts index 8bc04baad..53434b5a7 100644 --- a/src/hooks/useMetaplexMetadata.ts +++ b/src/hooks/useMetaplexMetadata.ts @@ -7,7 +7,7 @@ import { toMetadata, } from '@metaplex-foundation/js' import { NATIVE_MINT } from '@solana/spl-token' -import { PublicKey } from '@solana/web3.js' +import { AccountInfo, PublicKey } from '@solana/web3.js' import { useMemo } from 'react' import { useAsync } from 'react-async-hook' @@ -27,6 +27,27 @@ async function getMetadata(uri: string | undefined): Promise { } } +export const METADATA_PARSER: TypedAccountParser = ( + publicKey: PublicKey, + account: AccountInfo, +) => { + return toMetadata( + parseMetadataAccount({ + ...account, + lamports: sol(account.lamports), + data: account.data, + publicKey, + }), + ) +} + +export function getMetadataId(mint: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('metadata', 'utf-8'), MPL_PID.toBuffer(), mint.toBuffer()], + MPL_PID, + )[0] +} + export function useMetaplexMetadata(mint: PublicKey | undefined): { loading: boolean metadata: Metadata | undefined @@ -37,26 +58,15 @@ export function useMetaplexMetadata(mint: PublicKey | undefined): { } { const metadataAddr = useMemo(() => { if (mint) { - return PublicKey.findProgramAddressSync( - [Buffer.from('metadata', 'utf-8'), MPL_PID.toBuffer(), mint.toBuffer()], - MPL_PID, - )[0] + return getMetadataId(mint) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [mint?.toBase58()]) - const parser: TypedAccountParser = useMemo(() => { - return (publicKey, account) => { - return toMetadata( - parseMetadataAccount({ - ...account, - lamports: sol(account.lamports), - data: account.data, - publicKey, - }), - ) - } - }, []) - const { info: metadataAcc, loading } = useAccount(metadataAddr, parser) + + const { info: metadataAcc, loading } = useAccount( + metadataAddr, + METADATA_PARSER, + ) const { result: json, loading: jsonLoading } = useAsync(getMetadata, [ metadataAcc?.uri, ]) diff --git a/src/locales/en.ts b/src/locales/en.ts index 4a7c3e765..4aaaa8184 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -952,8 +952,7 @@ export default { newOwner: 'New Owner', oldAddress: 'Old Address', oldOwner: 'Old Owner', - owner: 'Owner', - payee: 'Payee {{index}}', + owner: 'Owner {{index}}', pending: { inProcess: 'In Process', pending: 'Pending', diff --git a/src/types/activity.ts b/src/types/activity.ts index 8b11bd691..01cbaf275 100644 --- a/src/types/activity.ts +++ b/src/types/activity.ts @@ -1,49 +1,21 @@ export type Activity = { - account?: null | string address?: null | string amount?: null | number - amountToSeller?: null | number buyer?: null | string - elevation?: null | number - endEpoch?: null | number + feePayer?: string fee?: null | number - gain?: null | number gateway?: null | string hash: string height?: null | number - lat?: null | number - lng?: null | number - location?: null | string - memo?: null | string - newAddress?: null | string - newOwner?: null | string - nonce?: null | number - oldAddress?: null | string - oldOwner?: null | string - owner?: null | string payments?: null | Array pending?: null | boolean - rewards?: null | Array - seller?: null | string - stake?: null | number - stakeAmount?: null | number - stakingFee?: null | number - startEpoch?: null | number time?: null | number - mint?: null | string type: string } export type Payment = { amount: number memo?: null | string - payee: string + owner: string mint?: null | string } - -export type Reward = { - account?: null | string - amount: number - gateway?: null | string - type: string -} diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index dbb390041..a4eb9883d 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -79,7 +79,6 @@ import { Keypair, LAMPORTS_PER_SOL, Logs, - ParsedInstruction, ParsedTransactionWithMeta, PublicKey, SignatureResult, @@ -116,9 +115,9 @@ import { Mints, } from './constants' import { getH3Location } from './h3' +import { decimalSeparator, groupSeparator } from './i18n' import * as Logger from './logger' import sleep from './sleep' -import { decimalSeparator, groupSeparator } from './i18n' export function humanReadable( amount?: BN, @@ -1450,6 +1449,8 @@ export const solInstructionsToActivity = ( activity.fee = meta?.fee activity.height = slot + activity.feePayer = + parsedTxn.transaction.message.accountKeys[0].pubkey.toBase58() if (blockTime) { activity.time = blockTime @@ -1462,16 +1463,22 @@ export const solInstructionsToActivity = ( const preBalance = preTokenBalances.find( ({ accountIndex }) => accountIndex === post.accountIndex, ) - const pre = preBalance || { uiTokenAmount: { uiAmount: 0 } } + const pre = preBalance || { + uiTokenAmount: { uiAmount: 0 }, + owner: post.owner, + } const preAmount = pre.uiTokenAmount.uiAmount || 0 const postAmount = post.uiTokenAmount.uiAmount || 0 const amount = postAmount - preAmount - const p: Payment = { - amount, - payee: '', - mint: post.mint, + if (amount !== 0 && !Number.isNaN(amount)) { + const p: Payment = { + amount, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + owner: (post.owner || pre.owner)!, + mint: post.mint, + } + payments = [...payments, p] } - payments = [...payments, p] }) activity.payments = payments } @@ -1483,13 +1490,6 @@ export const solInstructionsToActivity = ( if (activity.type === 'unknown') return - const payment = activity.payments?.[0] - if (payment && payment.mint === DC_MINT.toBase58()) { - activity.type = payment.payee !== activity.payer ? 'dc_delegate' : 'dc_mint' - activity.amount = payment.amount - activity.mint = payment.mint - } - return activity } From 50b7392bf711eab9782710e771e779ffe2f1303a Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 3 Aug 2023 11:48:58 -0500 Subject: [PATCH 08/23] Simplify balance hook --- src/utils/Balance.tsx | 175 ++++-------------------------------------- 1 file changed, 13 insertions(+), 162 deletions(-) diff --git a/src/utils/Balance.tsx b/src/utils/Balance.tsx index 8f26a2e85..c544d1461 100644 --- a/src/utils/Balance.tsx +++ b/src/utils/Balance.tsx @@ -1,18 +1,15 @@ import Balance, { - CurrencyType, DataCredits, IotTokens, MobileTokens, NetworkTokens, SolTokens, TestNetworkTokens, - Ticker, } from '@helium/currency' import { getOraclePrice } from '@helium/currency-utils' -import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' -import { PublicKey } from '@solana/web3.js' +import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' +import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' import BN from 'bn.js' -import { round } from 'lodash' import React, { ReactNode, createContext, @@ -35,7 +32,6 @@ import { useAppDispatch } from '../store/store' import { AccountBalance, BalanceInfo, TokenAccount } from '../types/balance' import StoreAtaBalance from './StoreAtaBalance' import StoreSolBalance from './StoreSolBalance' -import { accountCurrencyType } from './accountUtils' import { decimalSeparator, groupSeparator } from './i18n' import { humanReadable } from './solanaUtils' import { useBalanceHistory } from './useBalanceHistory' @@ -57,7 +53,7 @@ const useBalanceHook = () => { ) const allBalances = useSelector((state: RootState) => state.balances.balances) - const { convertToCurrency, currency: currencyRaw } = useAppStorage() + const { currency: currencyRaw } = useAppStorage() const currency = useMemo(() => currencyRaw?.toLowerCase(), [currencyRaw]) @@ -100,9 +96,7 @@ const useBalanceHook = () => { const solanaPrice = useMemo(() => { if (!tokenPrices?.solana) return - const price = tokenPrices.solana[currency] - - return new Balance(price, CurrencyType.usd) + return tokenPrices.solana[currency] }, [currency, tokenPrices]) useEffect(() => { @@ -127,70 +121,11 @@ const useBalanceHook = () => { [hntToDcPrice], ) - const floatToBalance = useCallback( - (value: number, ticker: Ticker) => { - if (!currentAccount) { - console.warn('Cannot convert float to balance for nil account') - return - } - return Balance.fromFloatAndTicker(value, ticker) - }, - [currentAccount], - ) - - const bonesToBalance = useCallback( - (v: number | undefined | null, ticker: Ticker | null | undefined) => { - return Balance.fromIntAndTicker(v || 0, ticker || 'HNT') - }, - [], - ) - - const intToBalance = useCallback( - (opts: { intValue?: number }) => { - if (!opts.intValue === undefined || !currentAccount) { - console.warn('Cannot convert int to balance') - return - } - return new Balance( - opts.intValue, - accountCurrencyType(currentAccount.address, undefined), - ) - }, - [currentAccount], - ) - - const toPreferredCurrencyString = useCallback( - ( - balance?: Balance, - opts?: { maxDecimalPlaces?: number; showTicker?: boolean }, - ): Promise => { - if (!balance) { - return new Promise((resolve) => resolve('')) - } - const multiplier = tokenPrices?.helium[currency] || 0 - - const showAsHnt = - !convertToCurrency || - !multiplier || - balance?.type.ticker === CurrencyType.dataCredit.ticker || - balance?.type.ticker === CurrencyType.testNetworkToken.ticker - - if (!showAsHnt) { - const convertedValue = multiplier * (balance?.floatBalance || 0) - return CurrencyFormatter.format(convertedValue, currency) - } - return new Promise((resolve) => - resolve(balanceToString(balance, opts)), - ) - }, - [convertToCurrency, currency, tokenPrices], - ) - const getBalance = useCallback( (mint: PublicKey, atas: Required[]) => { const mintStr = mint.toBase58() const ata = atas.find((a) => a.mint === mintStr) - return ata?.balance || 0 + return new BN(ata?.balance || 0) }, [], ) @@ -205,37 +140,24 @@ const useBalanceHook = () => { const solToken = accountBalancesForCluster?.sol - const solBalance = Balance.fromIntAndTicker(solToken?.balance || 0, 'SOL') + const solBalance = solToken?.balance const solPrice = tokenPrices?.solana?.[currency] || 0 - const solAmount = solBalance?.floatBalance - const solValue = solPrice * solAmount + const solValue = solPrice * (solBalance / LAMPORTS_PER_SOL) const formattedSolValue = await CurrencyFormatter.format(solValue, currency) - const hntBalance = Balance.fromIntAndTicker( - getBalance(HNT_MINT, atas), - 'HNT', - ) + const hntBalance = getBalance(HNT_MINT, atas) const hntPrice = tokenPrices?.helium?.[currency] || 0 - const hntAmount = hntBalance?.floatBalance - const hntValue = hntPrice * hntAmount + const hntValue = hntPrice * toNumber(hntBalance, 8) const formattedHntValue = await CurrencyFormatter.format(hntValue, currency) - const iotBalance = Balance.fromIntAndTicker( - getBalance(IOT_MINT, atas), - 'IOT', - ) + const iotBalance = getBalance(IOT_MINT, atas) const iotPrice = tokenPrices?.['helium-iot']?.[currency] || 0 - const iotAmount = iotBalance?.floatBalance - const iotValue = iotPrice * iotAmount + const iotValue = iotPrice * toNumber(iotBalance, 6) const formattedIotValue = await CurrencyFormatter.format(iotValue, currency) - const mobileBalance = Balance.fromIntAndTicker( - getBalance(MOBILE_MINT, atas), - 'MOBILE', - ) + const mobileBalance = getBalance(MOBILE_MINT, atas) const mobilePrice = tokenPrices?.['helium-mobile']?.[currency] || 0 - const mobileAmount = mobileBalance?.floatBalance - const mobileValue = mobilePrice * mobileAmount + const mobileValue = mobilePrice * toNumber(mobileBalance, 6) const formattedMobileValue = await CurrencyFormatter.format( mobileValue, currency, @@ -257,15 +179,6 @@ const useBalanceHook = () => { formattedMobileValue, formattedSolValue, formattedTotal, - hntBalance, - hntValue, - iotBalance, - iotValue, - mobileBalance, - mobileValue, - solBalance, - solToken, - solValue, } }, [ allBalances, @@ -300,96 +213,34 @@ const useBalanceHook = () => { setBalanceInfo(tokenInfo) }, [cluster, prevCluster, prevSolAddress, solanaAddress, tokenInfo]) - const toCurrencyString = useCallback( - ( - balance: Balance, - ticker: Ticker = 'HNT', - ): Promise => { - const defaultResponse = new Promise((resolve) => resolve('')) - - const bal = Balance.fromIntAndTicker(balance.integerBalance, ticker) - - let value = 0 - switch (ticker) { - case 'HNT': - value = tokenPrices?.helium[currency] || 0 - break - case 'SOL': - value = tokenPrices?.solana[currency] || 0 - break - case 'IOT': - value = tokenPrices?.['helium-iot'][currency] || 0 - break - case 'MOBILE': - value = tokenPrices?.['helium-mobile'][currency] || 0 - break - } - - if (!value) return defaultResponse - - return CurrencyFormatter.format(value * bal.floatBalance, currency) - }, - [currency, tokenPrices], - ) - - const toUsd = useCallback( - (balance?: Balance): number => { - if (!balance) { - return 0 - } - const multiplier = tokenPrices?.helium?.usd || 0 - - return round(multiplier * (balance?.floatBalance || 0), 2) - }, - [tokenPrices], - ) - return { balanceHistory, - bonesToBalance, dcToNetworkTokens, - floatToBalance, ...balanceInfo, - intToBalance, networkTokensToDc, oracleDateTime, oraclePrice: hntToDcPrice, solanaPrice, - toCurrencyString, - toPreferredCurrencyString, - toUsd, tokenAccounts, } } const initialState = { balanceHistory: [] as AccountBalance[], - bonesToBalance: () => new Balance(0, CurrencyType.networkToken), dcToNetworkTokens: () => undefined, - floatToBalance: () => undefined, formattedDcValue: '', formattedHntValue: '', formattedIotValue: '', formattedMobileValue: '', formattedSolValue: '', formattedTotal: undefined, - hntBalance: new Balance(0, CurrencyType.networkToken), - intToBalance: () => undefined, - iotBalance: new Balance(0, CurrencyType.iot), - mobileBalance: new Balance(0, CurrencyType.mobile), networkTokensToDc: () => undefined, oracleDateTime: undefined, oraclePrice: undefined, solanaPrice: undefined, - solBalance: new Balance(0, CurrencyType.solTokens), solBalancesLoading: false, - toCurrencyString: () => new Promise((resolve) => resolve('')), - toPreferredCurrencyString: () => - new Promise((resolve) => resolve('')), - toUsd: () => 0, atas: [], updating: false, - solToken: undefined, tokenAccounts: undefined, } const BalanceContext = From 679ac0dcfb278f737f0ff403da459427e85adc36 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 3 Aug 2023 14:30:00 -0500 Subject: [PATCH 09/23] Support payment QRs --- src/features/payment/PaymentScreen.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 591f87b77..32a40fb5f 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -80,11 +80,15 @@ type LinkedPayment = { amount?: string payee: string mint?: string + defaultTokenType?: string } const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { if (opts.payments) { - return JSON.parse(opts.payments) + return JSON.parse(opts.payments).map((p: LinkedPayment) => ({ + ...p, + mint: p.mint || Mints[p.defaultTokenType?.toUpperCase() as Ticker], + })) } if (opts.payee) { return [ From 87087c040150a7b719444a770a4a6c34a8129d03 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 3 Aug 2023 14:34:50 -0500 Subject: [PATCH 10/23] Ensure requests work --- src/components/HNTKeyboard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/HNTKeyboard.tsx b/src/components/HNTKeyboard.tsx index 6256d7221..3d52b6d34 100644 --- a/src/components/HNTKeyboard.tsx +++ b/src/components/HNTKeyboard.tsx @@ -249,7 +249,7 @@ const HNTKeyboardSelector = forwardRef( {!loadingMeta && ( {t('hntKeyboard.enterAmount', { - ticker: symbol, + ticker: symbol || '', })} )} @@ -465,7 +465,7 @@ const HNTKeyboardSelector = forwardRef( numberOfLines={1} adjustsFontSizeToFit > - {`${value || '0'} ${symbol}`} + {`${value || '0'} ${symbol || ''}`} {payer && networkFee && ( Date: Thu, 3 Aug 2023 15:22:06 -0500 Subject: [PATCH 11/23] Rm dead code --- package.json | 1 - src/App.tsx | 50 +- src/components/LedgerBurnModal.tsx | 163 ------- src/components/LedgerPayment.tsx | 163 ------- src/components/RewardItem.tsx | 35 +- src/components/TokenIcon.tsx | 60 +-- .../account/AccountManageTokenListScreen.tsx | 3 +- .../account/AccountTokenCurrencyBalance.tsx | 3 +- src/features/account/AccountTokenScreen.tsx | 5 +- src/features/account/TokenListItem.tsx | 3 +- src/features/burn/BurnScreen.tsx | 429 ++++++++---------- .../collectables/ClaimAllRewardsScreen.tsx | 6 +- src/features/collectables/HotspotList.tsx | 142 +++--- src/features/dappLogin/DappAccount.tsx | 139 ------ src/features/dappLogin/DappConnect.tsx | 80 ---- src/features/dappLogin/DappLoginScreen.tsx | 300 ------------ .../dappLogin/WalletConnectProvider.tsx | 300 ------------ src/features/home/homeTypes.ts | 3 +- src/features/payment/PaymentCard.tsx | 31 +- src/features/payment/PaymentScreen.tsx | 6 +- src/features/payment/PaymentTypeSelector.tsx | 97 ---- src/features/swaps/swapTypes.ts | 1 - src/locales/en.ts | 23 - src/navigation/RootNavigator.tsx | 6 - src/navigation/rootTypes.ts | 1 - src/types/balance.ts | 18 - src/types/solana.ts | 16 - src/utils/Balance.tsx | 49 +- src/utils/accountUtils.ts | 36 +- src/utils/linking.ts | 8 +- yarn.lock | 15 +- 31 files changed, 345 insertions(+), 1847 deletions(-) delete mode 100644 src/components/LedgerBurnModal.tsx delete mode 100644 src/components/LedgerPayment.tsx delete mode 100644 src/features/dappLogin/DappAccount.tsx delete mode 100644 src/features/dappLogin/DappConnect.tsx delete mode 100644 src/features/dappLogin/DappLoginScreen.tsx delete mode 100644 src/features/dappLogin/WalletConnectProvider.tsx delete mode 100644 src/features/payment/PaymentTypeSelector.tsx diff --git a/package.json b/package.json index 2c450200a..0392e6c92 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "@helium/address": "4.6.2", "@helium/circuit-breaker-sdk": "^0.2.16", "@helium/crypto-react-native": "4.8.0", - "@helium/currency": "4.11.1", "@helium/currency-utils": "0.1.1", "@helium/data-credits-sdk": "0.2.16", "@helium/distributor-oracle": "^0.2.16", diff --git a/src/App.tsx b/src/App.tsx index ee23e4e90..794e5253f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,6 @@ import OneSignal, { OpenedEvent } from 'react-native-onesignal' import { SafeAreaProvider } from 'react-native-safe-area-context' import NetworkAwareStatusBar from './components/NetworkAwareStatusBar' import SplashScreen from './components/SplashScreen' -import WalletConnectProvider from './features/dappLogin/WalletConnectProvider' import LockScreen from './features/lock/LockScreen' import OnboardingProvider from './features/onboarding/OnboardingProvider' import SecurityScreen from './features/security/SecurityScreen' @@ -120,32 +119,29 @@ const App = () => { commitment="confirmed" connection={connection} > - - {accountsRestored && ( - <> - - - - - - - - - - - - - )} - + {accountsRestored && ( + <> + + + + + + + + + + + + + )} )} diff --git a/src/components/LedgerBurnModal.tsx b/src/components/LedgerBurnModal.tsx deleted file mode 100644 index b0915ba5c..000000000 --- a/src/components/LedgerBurnModal.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { - forwardRef, - memo, - ReactNode, - Ref, - useCallback, - useImperativeHandle, - useMemo, - useRef, -} from 'react' -import { BottomSheetBackdrop, BottomSheetModal } from '@gorhom/bottom-sheet' -import { Edge } from 'react-native-safe-area-context' -import { TokenBurnV1 } from '@helium/transactions' -import Ledger from '@assets/images/ledger.svg' -import { useTranslation } from 'react-i18next' -import { useColors, useOpacity } from '@theme/themeHooks' -import useAlert from '@hooks/useAlert' -import useBackHandler from '@hooks/useBackHandler' -import useLedger from '@hooks/useLedger' -import { signLedgerBurn } from '../utils/heliumLedger' -import { LedgerDevice } from '../storage/cloudStorage' -import HandleBasic from './HandleBasic' -import SafeAreaBox from './SafeAreaBox' -import Box from './Box' -import Text from './Text' -import * as Logger from '../utils/logger' - -type ShowOptions = { - ledgerDevice: LedgerDevice - unsignedTxn: TokenBurnV1 - txnJson: string - accountIndex: number -} - -export type LedgerBurnModalRef = { - show: (opts: ShowOptions) => void - hide: () => void -} - -type Props = { - children: ReactNode - onConfirm: (opts: { txn: TokenBurnV1; txnJson: string }) => void - onError: (error: Error) => void - title: string - subtitle: string -} -const LedgerBurnModal = forwardRef( - ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - { children, onConfirm, onError, title, subtitle }: Props, - ref: Ref, - ) => { - useImperativeHandle(ref, () => ({ show, hide })) - const bottomSheetModalRef = useRef(null) - const { backgroundStyle } = useOpacity('surfaceSecondary', 1) - const { showOKAlert } = useAlert() - const { t } = useTranslation() - const { primaryText } = useColors() - const { getTransport } = useLedger() - const snapPoints = useMemo(() => { - return [600] - }, []) - const { handleDismiss, setIsShowing } = useBackHandler(bottomSheetModalRef) - - const show = useCallback( - async (opts: ShowOptions) => { - bottomSheetModalRef.current?.present() - setIsShowing(true) - try { - const nextTransport = await getTransport( - opts.ledgerDevice.id, - opts.ledgerDevice.type, - ) - if (!nextTransport) { - showOKAlert({ - title: t('ledger.deviceNotFound.title'), - message: t('addressBook.deviceNotFound.message'), - }) - return - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const payment = await signLedgerBurn( - nextTransport, - opts.unsignedTxn, - opts.accountIndex, - ) - // onConfirm({ txn: payment.txn, txnJson: opts.txnJson }) - bottomSheetModalRef.current?.dismiss() - } catch (error) { - // in this case, user is likely not on Helium app - Logger.error(error) - onError(error as Error) - bottomSheetModalRef.current?.dismiss() - } - }, - [ - getTransport, - // onConfirm, - onError, - setIsShowing, - showOKAlert, - t, - ], - ) - - const hide = useCallback(() => { - bottomSheetModalRef.current?.dismiss() - }, []) - - const renderBackdrop = useCallback( - (props) => ( - - ), - [], - ) - - const renderHandle = useCallback(() => { - return - }, []) - - const safeEdges = useMemo(() => ['bottom'] as Edge[], []) - - return ( - <> - - - - - - - {title} - - - {subtitle} - - - - {children} - - ) - }, -) - -export default memo(LedgerBurnModal) diff --git a/src/components/LedgerPayment.tsx b/src/components/LedgerPayment.tsx deleted file mode 100644 index 5828f523c..000000000 --- a/src/components/LedgerPayment.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { - forwardRef, - memo, - ReactNode, - Ref, - useCallback, - useImperativeHandle, - useMemo, - useRef, - useState, -} from 'react' -import { BottomSheetBackdrop, BottomSheetModal } from '@gorhom/bottom-sheet' -import { useTranslation } from 'react-i18next' -import { Edge } from 'react-native-safe-area-context' -import { PaymentV2 } from '@helium/transactions' -import Ledger from '@assets/images/ledger.svg' -import { Ticker } from '@helium/currency' -import { useColors, useOpacity } from '@theme/themeHooks' -import useAlert from '@hooks/useAlert' -import useBackHandler from '@hooks/useBackHandler' -import useLedger from '@hooks/useLedger' -import SafeAreaBox from './SafeAreaBox' -import HandleBasic from './HandleBasic' -import Text from './Text' -import Box from './Box' -import { LedgerDevice } from '../storage/cloudStorage' -import { SendDetails } from '../utils/linking' - -type ShowOptions = { - payments: SendDetails[] - ledgerDevice: LedgerDevice - address: string - accountIndex: number - speculativeNonce: number -} - -export type LedgerPaymentRef = { - show: (opts: ShowOptions) => void - hide: () => void -} - -type Props = { - children: ReactNode - onConfirm: (opts: { txn: PaymentV2; txnJson: string }) => void - onError: (error: Error) => void - ticker: Ticker -} -const LedgerPaymentSelector = forwardRef( - ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - { children, onConfirm, onError, ticker }: Props, - ref: Ref, - ) => { - useImperativeHandle(ref, () => ({ show, hide })) - const { showOKAlert } = useAlert() - const { t } = useTranslation() - const bottomSheetModalRef = useRef(null) - const { backgroundStyle } = useOpacity('surfaceSecondary', 1) - const { primaryText } = useColors() - const [options, setOptions] = useState() - const { getTransport } = useLedger() - const snapPoints = useMemo(() => { - return [600] - }, []) - const { handleDismiss, setIsShowing } = useBackHandler(bottomSheetModalRef) - - const show = useCallback( - async (opts: ShowOptions) => { - setOptions(opts) - bottomSheetModalRef.current?.present() - setIsShowing(true) - try { - const nextTransport = await getTransport( - opts.ledgerDevice.id, - opts.ledgerDevice.type, - ) - if (!nextTransport) { - showOKAlert({ - title: t('ledger.deviceNotFound.title'), - message: t('addressBook.deviceNotFound.message'), - }) - return - } - - // TODO: Implement Solana Ledger Payment - - // const payment = await signLedgerPayment( - // nextTransport, - // unsignedTxn, - // opts.accountIndex, - // ) - // onConfirm({ txn: payment, txnJson }) - bottomSheetModalRef.current?.dismiss() - } catch (error) { - // in this case, user is likely not on Helium app - console.error(error) - onError(error as Error) - bottomSheetModalRef.current?.dismiss() - } - }, - [getTransport, onError, setIsShowing, showOKAlert, t], - ) - - const hide = useCallback(() => { - bottomSheetModalRef.current?.dismiss() - }, []) - - const renderBackdrop = useCallback( - (props) => ( - - ), - [], - ) - - const renderHandle = useCallback(() => { - return - }, []) - - const safeEdges = useMemo(() => ['bottom'] as Edge[], []) - - return ( - <> - - - - - - - {t('ledger.payment.title')} - - - {t('ledger.payment.subtitle', { - name: options?.ledgerDevice.name, - })} - - - - {children} - - ) - }, -) - -export default memo(LedgerPaymentSelector) diff --git a/src/components/RewardItem.tsx b/src/components/RewardItem.tsx index 012f621aa..3d81c0d0d 100644 --- a/src/components/RewardItem.tsx +++ b/src/components/RewardItem.tsx @@ -1,31 +1,26 @@ -import { Ticker } from '@helium/currency' +import RewardBG from '@assets/images/rewardBg.svg' +import { useMint } from '@helium/helium-react-hooks' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { BoxProps } from '@shopify/restyle' +import { PublicKey } from '@solana/web3.js' +import { Theme } from '@theme/theme' +import { humanReadable } from '@utils/solanaUtils' import BN from 'bn.js' import React, { memo, useMemo } from 'react' -import RewardBG from '@assets/images/rewardBg.svg' -import { Theme } from '@theme/theme' -import { IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' -import { useMint } from '@helium/helium-react-hooks' -import { formatLargeNumber } from '@utils/accountUtils' -import BigNumber from 'bignumber.js' import Box from './Box' -import TokenIcon from './TokenIcon' import Text from './Text' +import TokenIcon from './TokenIcon' -type RewardItemProps = { ticker: Ticker; amount: BN } & BoxProps - -const RewardItem = ({ ticker, amount, ...rest }: RewardItemProps) => { - const { info: iotMint } = useMint(IOT_MINT) - const { info: mobileMint } = useMint(MOBILE_MINT) +type RewardItemProps = { mint: PublicKey; amount: BN } & BoxProps +const RewardItem = ({ mint, amount, ...rest }: RewardItemProps) => { + const decimals = useMint(mint)?.info?.decimals + const { json, symbol } = useMetaplexMetadata(mint) const pendingRewardsString = useMemo(() => { if (!amount) return - const decimals = - ticker === 'MOBILE' ? mobileMint?.info.decimals : iotMint?.info.decimals - const num = toNumber(amount, decimals || 6) - return formatLargeNumber(new BigNumber(num)) - }, [mobileMint, iotMint, amount, ticker]) + return humanReadable(amount, decimals || 6) + }, [amount, decimals]) return ( { - + { {pendingRewardsString} - {ticker} + {symbol} ) diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx index e4c461097..8b6d0796a 100644 --- a/src/components/TokenIcon.tsx +++ b/src/components/TokenIcon.tsx @@ -1,26 +1,12 @@ -import TokenDC from '@assets/images/tokenDC.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import TokenIOT from '@assets/images/tokenIOT.svg' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' -import TokenSolWhite from '@assets/images/tokenSOL.svg' -import TokenSOL from '@assets/images/tokenSolana.svg' -import { Ticker } from '@helium/currency' -import { useColors } from '@theme/themeHooks' import React from 'react' import { Image } from 'react-native' -import BackgroundFill from './BackgroundFill' -import Box from './Box' type Props = { - ticker?: Ticker size?: number - white?: boolean img?: string } -const TokenIcon = ({ ticker, size = 40, white, img }: Props) => { - const colors = useColors() - +const TokenIcon = ({ size = 40, img }: Props) => { if (img) { return ( { ) } - switch (ticker) { - default: - return null - case 'HNT': - return - case 'MOBILE': - return - case 'IOT': - return - case 'DC': - return - case 'HST': - return - case 'SOL': - if (white) { - return ( - - - - ) - } - return ( - - - - - ) - } + return null } export default TokenIcon diff --git a/src/features/account/AccountManageTokenListScreen.tsx b/src/features/account/AccountManageTokenListScreen.tsx index 8a6230ab6..f286f7b47 100644 --- a/src/features/account/AccountManageTokenListScreen.tsx +++ b/src/features/account/AccountManageTokenListScreen.tsx @@ -5,7 +5,6 @@ import SafeAreaBox from '@components/SafeAreaBox' import Text from '@components/Text' import TokenIcon from '@components/TokenIcon' import TouchableContainer from '@components/TouchableContainer' -import { Ticker } from '@helium/currency' import { useOwnedAmount } from '@helium/helium-react-hooks' import { DC_MINT } from '@helium/spl-utils' import { useCurrentWallet } from '@hooks/useCurrentWallet' @@ -78,7 +77,7 @@ const CheckableTokenListItem = ({ )} diff --git a/src/features/account/AccountTokenCurrencyBalance.tsx b/src/features/account/AccountTokenCurrencyBalance.tsx index 4615fe7fa..a6eeffb21 100644 --- a/src/features/account/AccountTokenCurrencyBalance.tsx +++ b/src/features/account/AccountTokenCurrencyBalance.tsx @@ -1,10 +1,9 @@ import React, { useMemo } from 'react' -import { Ticker } from '@helium/currency' import Text, { TextProps } from '@components/Text' import { useBalance } from '../../utils/Balance' type Props = { - ticker: Ticker | 'ALL' + ticker: string } & TextProps const AccountTokenCurrencyBalance = ({ ticker, ...textProps }: Props) => { diff --git a/src/features/account/AccountTokenScreen.tsx b/src/features/account/AccountTokenScreen.tsx index 3d7bd8dca..66889159a 100644 --- a/src/features/account/AccountTokenScreen.tsx +++ b/src/features/account/AccountTokenScreen.tsx @@ -13,7 +13,6 @@ import BottomSheet, { BottomSheetFlatList, WINDOW_HEIGHT, } from '@gorhom/bottom-sheet' -import { Ticker } from '@helium/currency' import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' import useLayoutHeight from '@hooks/useLayoutHeight' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' @@ -450,7 +449,7 @@ const AccountTokenScreen = () => { /> {!!symbol && ( @@ -473,7 +472,7 @@ const AccountTokenScreen = () => { {!!symbol && ( { )} diff --git a/src/features/burn/BurnScreen.tsx b/src/features/burn/BurnScreen.tsx index 47c14fdbf..543ff8fa9 100644 --- a/src/features/burn/BurnScreen.tsx +++ b/src/features/burn/BurnScreen.tsx @@ -5,9 +5,6 @@ import AccountSelector, { AccountSelectorRef, } from '@components/AccountSelector' import Box from '@components/Box' -import LedgerBurnModal, { - LedgerBurnModalRef, -} from '@components/LedgerBurnModal' import SafeAreaBox from '@components/SafeAreaBox' import SubmitButton from '@components/SubmitButton' import Text from '@components/Text' @@ -25,7 +22,6 @@ import { MOBILE_MINT, humanReadable, } from '@helium/spl-utils' -import { TokenBurnV1 } from '@helium/transactions' import useAlert from '@hooks/useAlert' import { useBN } from '@hooks/useBN' import { useCurrentWallet } from '@hooks/useCurrentWallet' @@ -85,7 +81,6 @@ const BurnScreen = () => { const navigation = useNavigation() const { t } = useTranslation() const { primaryText } = useColors() - const ledgerPaymentRef = useRef(null) const hitSlop = useHitSlop('l') const accountSelectorRef = useRef(null) const { submitDelegateDataCredits } = useSubmitTxn() @@ -203,24 +198,6 @@ const BurnScreen = () => { navigation.navigate('PaymentQrScanner') }, [navigation]) - const ledgerPaymentConfirmed = useCallback( - (_opts: { txn: TokenBurnV1; txnJson: string }) => { - console.error('Ledger payment not supported') - }, - [], - ) - - const handleLedgerError = useCallback( - async (error: Error) => { - await showOKAlert({ - title: t('generic.error'), - message: error.toString(), - }) - navigation.goBack() - }, - [navigation, showOKAlert, t], - ) - const insufficientFunds = useMemo(() => { if (!amountBalance || !dcBalance || !solBalance) return false @@ -334,231 +311,211 @@ const BurnScreen = () => { onContactSelected={handleContactSelected} hideCurrentAccount > - - - - - - - - - - {t(isDelegate ? 'delegate.title' : 'burn.title')} - - + - - + + - - - 1 - } - address={currentAccount?.address} - onPress={handleShowAccounts} - showBubbleArrow - marginHorizontal="l" - marginBottom="xs" - /> - - - - {isDelegate ? ( - { - setDelegateAddress(address) - handleAddressError({ - address, - }) - }} - handleAddressError={handleAddressError} - mint={DC_MINT} - address={delegateAddress} - amount={amountBalance} - hasError={hasError} - hideMemo - /> - ) : ( - <> - - - - {t('burn.amount')} - - - {amountBalance.toString()} - - - {t('payment.fee', { - value: humanReadable(FEE, 9), - })} - - - - - - {t('burn.equivalent')} - - - {amountInDc?.toString()} - - - - - - )} - - {submitError && ( - - - {submitError} - - - )} - + - + + + + + 1} + address={currentAccount?.address} + onPress={handleShowAccounts} + showBubbleArrow + marginHorizontal="l" + marginBottom="xs" + /> + + + + {isDelegate ? ( + { + setDelegateAddress(address) + handleAddressError({ + address, + }) + }} + handleAddressError={handleAddressError} mint={DC_MINT} - totalBalance={amountBalance} - feeTokenBalance={FEE} - errors={errors} + address={delegateAddress} + amount={amountBalance} + hasError={hasError} + hideMemo /> - - + - + + + {t('burn.amount')} + + + {amountBalance.toString()} + + + {t('payment.fee', { + value: humanReadable(FEE, 9), + })} + + + + + + {t('burn.equivalent')} + + + {amountInDc?.toString()} + + + + + + )} + + {submitError && ( + + + {submitError} + + + )} + + + + - - - + + + diff --git a/src/features/collectables/ClaimAllRewardsScreen.tsx b/src/features/collectables/ClaimAllRewardsScreen.tsx index ba6992240..43d03f559 100644 --- a/src/features/collectables/ClaimAllRewardsScreen.tsx +++ b/src/features/collectables/ClaimAllRewardsScreen.tsx @@ -13,7 +13,7 @@ import { IOT_LAZY_KEY, MOBILE_LAZY_KEY } from '@utils/constants' import BN from 'bn.js' import React, { memo, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { toNumber } from '@helium/spl-utils' +import { IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' import { CollectableNavigationProp } from './collectablesTypes' import { BalanceChange } from '../../solana/walletSignBottomSheetTypes' @@ -120,14 +120,14 @@ const ClaimAllRewardsScreen = () => { > {pendingMobileRewards && pendingMobileRewards.gt(new BN(0)) && ( )} {pendingIotRewards && pendingIotRewards.gt(new BN(0)) && ( diff --git a/src/features/collectables/HotspotList.tsx b/src/features/collectables/HotspotList.tsx index 0829780bf..cb0ea8cbc 100644 --- a/src/features/collectables/HotspotList.tsx +++ b/src/features/collectables/HotspotList.tsx @@ -1,36 +1,91 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { times } from 'lodash' -import { FlatList } from 'react-native-gesture-handler' -import { RefreshControl } from 'react-native' -import { useIsFocused, useNavigation } from '@react-navigation/native' -import { useTranslation } from 'react-i18next' -import BN from 'bn.js' -import listViewIcon from '@assets/images/listViewIcon.svg' import expandedViewIcon from '@assets/images/expandedViewIcon.svg' -import ListItem from '@components/ListItem' +import listViewIcon from '@assets/images/listViewIcon.svg' import BlurActionSheet from '@components/BlurActionSheet' -import { useColors } from '@theme/themeHooks' import Box from '@components/Box' import ButtonPressable from '@components/ButtonPressable' -import useHotspots from '@hooks/useHotspots' import CircleLoader from '@components/CircleLoader' -import useHaptic from '@hooks/useHaptic' +import ListItem from '@components/ListItem' +import TabBar from '@components/TabBar' import Text from '@components/Text' import TokenIcon from '@components/TokenIcon' -import TabBar from '@components/TabBar' import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' import { useMint } from '@helium/helium-react-hooks' +import { IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' +import useHaptic from '@hooks/useHaptic' +import useHotspots from '@hooks/useHotspots' +import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' +import { useIsFocused, useNavigation } from '@react-navigation/native' +import { PublicKey } from '@solana/web3.js' +import { useColors } from '@theme/themeHooks' import BigNumber from 'bignumber.js' +import BN from 'bn.js' +import { times } from 'lodash' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { RefreshControl } from 'react-native' +import { FlatList } from 'react-native-gesture-handler' +import { CompressedNFT, HotspotWithPendingRewards } from '../../types/solana' import { formatLargeNumber } from '../../utils/accountUtils' import HotspotCompressedListItem from './HotspotCompressedListItem' import HotspotListItem from './HotspotListItem' -import { CollectableNavigationProp } from './collectablesTypes' -import { CompressedNFT, HotspotWithPendingRewards } from '../../types/solana' import { NFTSkeleton } from './NftListItem' +import { CollectableNavigationProp } from './collectablesTypes' export const DEFAULT_PAGE_AMOUNT = 20 +function RewardItem({ + mint, + amount, + marginStart, + marginEnd, +}: { + mint: PublicKey + amount: BN | undefined + // eslint-disable-next-line @typescript-eslint/no-explicit-any + marginStart?: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + marginEnd?: any +}) { + const decimals = useMint(mint)?.info?.decimals + const { json, symbol } = useMetaplexMetadata(mint) + let realAmount = '' + if (amount) { + const num = toNumber(amount, decimals || 6) + realAmount = formatLargeNumber(new BigNumber(num)) + } + + return ( + + + + + + {realAmount} + + + {symbol} + + + + ) +} + const HotspotList = () => { const navigation = useNavigation() const { t } = useTranslation() @@ -42,9 +97,6 @@ const HotspotList = () => { DEFAULT_PAGE_AMOUNT, ) - const { info: iotMint } = useMint(IOT_MINT) - const { info: mobileMint } = useMint(MOBILE_MINT) - const tabBarOptions = useMemo( () => [ { @@ -144,49 +196,6 @@ const HotspotList = () => { [handleSetPageAmount, pageAmount, t], ) - const RewardItem = useCallback( - ({ ticker, amount, ...rest }) => { - const decimals = - ticker === 'IOT' ? iotMint?.decimals : mobileMint?.decimals - let realAmount = '' - if (amount) { - const num = toNumber(amount, decimals || 6) - realAmount = formatLargeNumber(new BigNumber(num)) - } - - return ( - - - - - - {realAmount} - - - {ticker} - - - - ) - }, - [iotMint, mobileMint], - ) - const onTabSelected = useCallback( (value) => { setTabSelected(value) @@ -226,11 +235,15 @@ const HotspotList = () => { - + {pageAmount && hotspotsWithMeta?.length >= pageAmount && ( { handleNavigateToClaimRewards, pendingIotRewards, pendingMobileRewards, - RewardItem, t, onTabSelected, tabSelected, diff --git a/src/features/dappLogin/DappAccount.tsx b/src/features/dappLogin/DappAccount.tsx deleted file mode 100644 index 1df7e60be..000000000 --- a/src/features/dappLogin/DappAccount.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react' -import { useTranslation } from 'react-i18next' -import Crowdspot from '@assets/images/crowdspot.svg' -import AddDapp from '@assets/images/addDapp.svg' -import DappEllipsis from '@assets/images/dapp-ellipsis.svg' -import { NetTypes as NetType, NetTypes } from '@helium/address' -import { ActivityIndicator } from 'react-native' -import AccountButton from '@components/AccountButton' -import AccountSelector, { - AccountSelectorRef, -} from '@components/AccountSelector' -import Box from '@components/Box' -import Text from '@components/Text' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { useAccountStorage } from '@storage/AccountStorageProvider' -import AccountIcon from '@components/AccountIcon' -import { useColors } from '@theme/themeHooks' - -type Props = { - onLogin: () => void - appName: string - onCancel: () => void - loading: boolean -} -const DappLogin = ({ onLogin, onCancel, appName, loading }: Props) => { - const { - currentAccount, - setCurrentAccount, - sortedMainnetAccounts, - currentNetworkAddress, - } = useAccountStorage() - const { t } = useTranslation() - const accountSelectorRef = useRef(null) - const colors = useColors() - - const isCrowdspot = useMemo( - () => appName.toLowerCase() === 'crowdspot', - [appName], - ) - - const handleAccountButtonPress = useCallback(() => { - if (!accountSelectorRef?.current) return - - accountSelectorRef.current.showAccountTypes(NetTypes.MAINNET)() - }, []) - - useEffect(() => { - if (currentAccount?.netType !== NetType.MAINNET) { - setCurrentAccount( - sortedMainnetAccounts.length ? sortedMainnetAccounts[0] : null, - ) - } - }, [currentAccount, setCurrentAccount, sortedMainnetAccounts]) - - return ( - - - - - {isCrowdspot ? ( - - ) : ( - - )} - - - - - - - {t('dappLogin.account.title', { - appName, - })} - - - {t('dappLogin.account.subtitle', { appName })} - - - - - - - {t('generic.cancel')} - - - - {loading ? ( - - ) : ( - - {t('dappLogin.login')} - - )} - - - - - ) -} - -export default memo(DappLogin) diff --git a/src/features/dappLogin/DappConnect.tsx b/src/features/dappLogin/DappConnect.tsx deleted file mode 100644 index e7be44d1f..000000000 --- a/src/features/dappLogin/DappConnect.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { memo, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import Crowdspot from '@assets/images/crowdspot.svg' -import AddDapp from '@assets/images/addDapp.svg' -import Box from '@components/Box' -import Text from '@components/Text' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { useColors } from '@theme/themeHooks' - -type Props = { appName: string; onApprove: () => void; onDeny: () => void } -const DappConnect = ({ appName, onApprove, onDeny }: Props) => { - const { t } = useTranslation() - const { primaryText } = useColors() - - const isCrowdspot = useMemo( - () => appName.toLowerCase() === 'crowdspot', - [appName], - ) - - return ( - - - {isCrowdspot ? ( - - ) : ( - - )} - - {t('dappLogin.connect.title', { appName })} - - - {t('dappLogin.connect.subtitle', { appName })} - - - - - - - {t('generic.cancel')} - - - - - {t('dappLogin.connect.continue')} - - - - - ) -} - -export default memo(DappConnect) diff --git a/src/features/dappLogin/DappLoginScreen.tsx b/src/features/dappLogin/DappLoginScreen.tsx deleted file mode 100644 index 97bbd47b2..000000000 --- a/src/features/dappLogin/DappLoginScreen.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { TokenBurnV1 } from '@helium/transactions' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react' -import { useTranslation } from 'react-i18next' -import { ActivityIndicator, Linking } from 'react-native' -import { useDebouncedCallback } from 'use-debounce/lib' -import Close from '@assets/images/close.svg' -import { useAsync } from 'react-async-hook' -import LedgerBurnModal, { - LedgerBurnModalRef, -} from '@components/LedgerBurnModal' -import SafeAreaBox from '@components/SafeAreaBox' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { useColors } from '@theme/themeHooks' -import useAlert from '@hooks/useAlert' -import Address from '@helium/address' -import { useAccountStorage } from '../../storage/AccountStorageProvider' -import { HomeNavigationProp } from '../home/homeTypes' -import { useWalletConnect } from './WalletConnectProvider' -import DappConnect from './DappConnect' -import DappAccount from './DappAccount' -import { - RootNavigationProp, - RootStackParamList, -} from '../../navigation/rootTypes' -import { getKeypair } from '../../storage/secureStorage' - -export const EMPTY_B58_ADDRESS = Address.fromB58( - '13PuqyWXzPYeXcF1B9ZRx7RLkEygeL374ZABiQdwRSNzASdA1sn', -) -const makeBurnTxn = async (opts: { payerB58: string }) => { - const { payerB58 } = opts - - const txn = new TokenBurnV1({ - amount: 1, - payer: Address.fromB58(payerB58), - payee: EMPTY_B58_ADDRESS, - nonce: 0, - memo: '', - }) - - const txnJson = { - type: txn.type, - payee: txn.payee?.b58 || '', - amount: 1, - payer: txn.payer?.b58, - nonce: txn.nonce, - fee: txn.fee, - memo: txn.memo, - } - - const keypair = await getKeypair(payerB58) - - if (!keypair) throw new Error('Keypair not found') - const signedTxn = await txn.sign({ payer: keypair }) - return { signedTxn, txnJson: JSON.stringify(txnJson), unsignedTxn: txn } -} - -type Route = RouteProp -const DappLoginScreen = () => { - const route = useRoute() - const navigation = useNavigation() - const rootNav = useNavigation() - const { params } = route - const { - allowLogin, - approvePair, - connectionState, - denyPair, - disconnect, - login, - loginRequest, - sessionProposal, - pairClient, - } = useWalletConnect() - const { t } = useTranslation() - const colors = useColors() - const { currentAccount } = useAccountStorage() - const { primaryText } = useColors() - const { showOKAlert } = useAlert() - const ledgerRef = useRef(null) - const hasRequestedPair = useRef(false) - const ledgerShown = useRef(false) - - useAsync(async () => { - if (params.uri.includes('wc:') && !hasRequestedPair.current) { - hasRequestedPair.current = true - try { - await pairClient(params.uri) - } catch (error) { - await showOKAlert({ - title: t('dappLogin.error', { - appName: sessionProposal?.params.proposer.metadata.name, - }), - message: (error as Error).toString(), - }) - } - } - }, [pairClient, params.callback, params.uri]) - - const goBack = useCallback(async () => { - await disconnect() - - if (navigation.canGoBack()) { - navigation.goBack() - } else { - rootNav.reset({ - index: 0, - routes: [{ name: 'TabBarNavigator' }], - }) - } - }, [disconnect, navigation, rootNav]) - - const handleDeny = useCallback(async () => { - await denyPair() - await goBack() - }, [denyPair, goBack]) - - const handleAllowLogin = useDebouncedCallback( - () => { - allowLogin() - }, - 1000, - { - leading: true, - trailing: false, - }, - ) - - const handleLogin = useCallback(async () => { - if (!currentAccount?.address) return - - try { - await approvePair(currentAccount.address) - } catch (error) { - await showOKAlert({ - title: t('dappLogin.error', { - appName: sessionProposal?.params.proposer.metadata.name, - }), - message: (error as Error).toString(), - }) - } - }, [approvePair, currentAccount, sessionProposal, showOKAlert, t]) - - useAsync(async () => { - if (!currentAccount?.address || !loginRequest) return - - const isLedger = !!currentAccount.ledgerDevice - - const { signedTxn, unsignedTxn, txnJson } = await makeBurnTxn({ - payerB58: currentAccount.address, - }) - - if (isLedger && currentAccount.ledgerDevice) { - if (ledgerShown.current) return - - ledgerShown.current = true - - ledgerRef.current?.show({ - unsignedTxn, - ledgerDevice: currentAccount.ledgerDevice, - accountIndex: currentAccount.accountIndex || 0, - txnJson, - }) - return - } - - if (!signedTxn) return - - try { - await login({ - txn: signedTxn.toString(), - address: currentAccount.address, - }) - } catch (error) { - await showOKAlert({ - title: t('dappLogin.error', { - appName: sessionProposal?.params.proposer.metadata.name, - }), - message: (error as Error).toString(), - }) - } - - await goBack() - - if (params.callback && (await Linking.canOpenURL(params.callback))) { - await Linking.openURL(params.callback) - } - }, [currentAccount, loginRequest]) - - const ledgerConfirmed = useCallback( - async ({ txn: signedTxn }: { txn: TokenBurnV1; txnJson: string }) => { - if (!currentAccount) return - - await login({ - txn: signedTxn.toString(), - address: currentAccount.address, - }) - - await goBack() - }, - [currentAccount, goBack, login], - ) - - const handleLedgerError = useCallback( - async (error: Error) => { - await showOKAlert({ - title: t('generic.error'), - message: error.toString(), - }) - await goBack() - }, - [goBack, showOKAlert, t], - ) - - const checkTimeoutError = useCallback(async () => { - if (connectionState !== 'undetermined') return - await showOKAlert({ - title: t('dappLogin.timeoutAlert.title'), - message: t('dappLogin.timeoutAlert.message'), - }) - await goBack() - }, [connectionState, goBack, showOKAlert, t]) - - // if connectionState doesn't update after 5 seconds show timeout error - useEffect(() => { - const timer = setTimeout(checkTimeoutError, 5000) - return () => { - clearTimeout(timer) - } - }, [connectionState, goBack, checkTimeoutError, showOKAlert, t]) - - const body = useMemo(() => { - if (!sessionProposal || connectionState === 'undetermined') { - return ( - - - - ) - } - if (connectionState === 'proposal') { - return ( - - ) - } - - return ( - - ) - }, [ - connectionState, - handleAllowLogin, - handleDeny, - handleLogin, - sessionProposal, - goBack, - primaryText, - ]) - - return ( - - - - - - {body} - - - ) -} - -export default memo(DappLoginScreen) diff --git a/src/features/dappLogin/WalletConnectProvider.tsx b/src/features/dappLogin/WalletConnectProvider.tsx deleted file mode 100644 index 5040c159d..000000000 --- a/src/features/dappLogin/WalletConnectProvider.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import React, { - createContext, - ReactNode, - useCallback, - useContext, - useState, -} from 'react' -import '@walletconnect/react-native-compat' -import SignClient from '@walletconnect/sign-client' -import { - PairingTypes, - SessionTypes, - SignClientTypes, -} from '@walletconnect/types' -import Config from 'react-native-config' -import * as Logger from '../../utils/logger' - -const breadcrumbOpts = { category: 'Wallet Connect' } - -const NAMESPACE = 'helium' -const MAINNET = `${NAMESPACE}:mainnet` -type ConnectionState = - | 'undetermined' - | 'proposal' - | 'allowed' - | 'denied' - | 'approving' - | 'approved' -const useWalletConnectHook = () => { - const [signClient, setSignClient] = useState() - const [sessionProposal, setSessionProposal] = - useState() - const [connectionState, setConnectionState] = - useState('undetermined') - const [loginRequest, setLoginRequest] = - useState() - const [approvalRequest, setApprovalRequest] = useState<{ topic: string }>() - - const pairClient = useCallback( - async (uri: string): Promise => { - Logger.breadcrumb('pair client requested', breadcrumbOpts) - - try { - let client = signClient - if (!client) { - Logger.breadcrumb('Begin Initialize Client', breadcrumbOpts) - - client = await SignClient.init({ - projectId: Config.WALLET_CONNECT_PROJECT_ID, - relayUrl: 'wss://relay.walletconnect.com', - metadata: { - name: 'Helium Wallet', - description: 'Helium Wallet', - url: Config.WALLET_CONNECT_METADATA_URL, - icons: [], - }, - }) - - Logger.breadcrumb('Client initialized', breadcrumbOpts) - - setSignClient(client) - - client.on('session_proposal', async (proposal) => { - Logger.breadcrumb('Session proposal created', breadcrumbOpts) - setSessionProposal(proposal) - setConnectionState('proposal') - }) - - client.on('session_request', async (loginReqEvent) => { - if (loginReqEvent.params.request.method !== 'personal_sign') return - - Logger.breadcrumb('Login request event created', breadcrumbOpts) - setLoginRequest(loginReqEvent) - }) - } else { - Logger.breadcrumb('Client already initialized', breadcrumbOpts) - } - - Logger.breadcrumb('client.pair - begin', breadcrumbOpts) - const pairResponse = await client.pair({ uri }) - Logger.breadcrumb('client.pair - success', breadcrumbOpts) - return pairResponse - } catch (err) { - Logger.breadcrumb('pairClient - fail', breadcrumbOpts) - Logger.error(err) - throw err - } - }, - [setLoginRequest, signClient], - ) - - const approvePair = useCallback( - async ( - address: string, - ): Promise< - | { - topic: string - acknowledged: () => Promise - } - | undefined - > => { - Logger.breadcrumb('approvePair', breadcrumbOpts) - - setConnectionState('approving') - - try { - if (!sessionProposal || !signClient) { - Logger.breadcrumb( - `Approve pair requested, but client not ready. ${JSON.stringify({ - loginProposal: !!sessionProposal, - walletClient: !!signClient, - })}`, - breadcrumbOpts, - ) - return await new Promise(() => {}) - } - - const nextApprovalResponse = await signClient.approve({ - id: sessionProposal.id, - namespaces: { - [NAMESPACE]: { - accounts: [`${MAINNET}:${address}`], - methods: ['personal_sign'], - events: [], - }, - }, - }) - - setConnectionState('approved') - - Logger.breadcrumb('approvePair - success', breadcrumbOpts) - setApprovalRequest(nextApprovalResponse) - return nextApprovalResponse - } catch (err) { - Logger.breadcrumb('approvePair - fail', breadcrumbOpts) - Logger.error(err) - throw err - } - }, - [sessionProposal, signClient], - ) - - const allowLogin = useCallback(() => { - Logger.breadcrumb('allowLogin', breadcrumbOpts) - - setConnectionState('allowed') - }, []) - - const denyPair = useCallback(async () => { - Logger.breadcrumb('denyPair', breadcrumbOpts) - - if (!sessionProposal || !signClient) { - Logger.breadcrumb( - `Deny pair requested, but client not ready. ${JSON.stringify({ - loginProposal: !!sessionProposal, - walletClient: !!signClient, - })}`, - breadcrumbOpts, - ) - return - } - - Logger.breadcrumb('denyPair - begin client reject', breadcrumbOpts) - const nextDenyResponse = await signClient.reject({ - id: sessionProposal.id, - reason: { code: 1000, message: 'denied by user' }, - }) - - Logger.breadcrumb('denyPair - client reject success', breadcrumbOpts) - - setConnectionState('denied') - - return nextDenyResponse - }, [sessionProposal, signClient]) - - const disconnect = useCallback(async () => { - Logger.breadcrumb('disconnect', breadcrumbOpts) - - try { - if (connectionState === 'undetermined') { - await denyPair() - } - - if (signClient) { - if (loginRequest?.topic || approvalRequest?.topic) { - Logger.breadcrumb('disconnect wallet client - begin', breadcrumbOpts) - await signClient.disconnect({ - topic: loginRequest?.topic || approvalRequest?.topic || '', - reason: { - code: 1, - message: 'finished', - }, - }) - Logger.breadcrumb( - 'disconnect wallet client - success', - breadcrumbOpts, - ) - } else if ( - sessionProposal?.id && - connectionState !== 'undetermined' && - connectionState !== 'denied' - ) { - signClient.reject({ - id: sessionProposal.id, - reason: { code: 2000, message: 'Login Process Closed' }, - }) - } - } - setSignClient(undefined) - setSessionProposal(undefined) - setLoginRequest(undefined) - setApprovalRequest(undefined) - setConnectionState('undetermined') - } catch (err) { - Logger.breadcrumb('disconnect - fail', breadcrumbOpts) - Logger.error(err) - throw err - } - }, [ - approvalRequest, - connectionState, - denyPair, - loginRequest, - sessionProposal, - signClient, - ]) - - const login = useCallback( - async (opts: { txn: string; address: string }) => { - Logger.breadcrumb('login', breadcrumbOpts) - - try { - if (!loginRequest || !signClient) { - Logger.breadcrumb( - `Login requested, but client not ready. ${JSON.stringify({ - loginProposal: !!sessionProposal, - walletClient: !!signClient, - })}`, - breadcrumbOpts, - ) - return - } - - const { topic } = loginRequest - - const responseBody = { - topic, - response: { - id: loginRequest.id, - jsonrpc: '2.0', - result: opts, - }, - } - await signClient.respond(responseBody) - Logger.breadcrumb('login - success', breadcrumbOpts) - } catch (err) { - Logger.breadcrumb('login - fail', breadcrumbOpts) - Logger.error(err) - throw err - } - }, - [sessionProposal, loginRequest, signClient], - ) - - return { - allowLogin, - approvePair, - connectionState, - denyPair, - disconnect, - login, - sessionProposal, - loginRequest, - pairClient, - } -} - -const initialState = { - approvePair: () => new Promise((resolve) => resolve(undefined)), - allowLogin: () => undefined, - connectionState: 'undetermined' as ConnectionState, - denyPair: () => new Promise((resolve) => resolve()), - disconnect: () => new Promise((resolve) => resolve()), - login: () => new Promise((resolve) => resolve()), - sessionProposal: undefined, - loginRequest: undefined, - pairClient: () => new Promise((resolve) => resolve()), -} - -const WalletConnectContext = - createContext>(initialState) -const { Provider } = WalletConnectContext - -const WalletConnectProvider = ({ children }: { children: ReactNode }) => { - return {children} -} - -export const useWalletConnect = () => useContext(WalletConnectContext) - -export default WalletConnectProvider diff --git a/src/features/home/homeTypes.ts b/src/features/home/homeTypes.ts index d0dd766bc..e1894b821 100644 --- a/src/features/home/homeTypes.ts +++ b/src/features/home/homeTypes.ts @@ -1,4 +1,3 @@ -import { Ticker } from '@helium/currency' import { StackNavigationProp } from '@react-navigation/stack' export type PaymentRouteParam = { @@ -8,7 +7,7 @@ export type PaymentRouteParam = { amount?: string memo?: string netType?: string - defaultTokenType?: Ticker + defaultTokenType?: string mint?: string } diff --git a/src/features/payment/PaymentCard.tsx b/src/features/payment/PaymentCard.tsx index 480b06d50..a03396536 100644 --- a/src/features/payment/PaymentCard.tsx +++ b/src/features/payment/PaymentCard.tsx @@ -1,5 +1,4 @@ import Box from '@components/Box' -import { LedgerPaymentRef } from '@components/LedgerPayment' import SubmitButton from '@components/SubmitButton' import Text from '@components/Text' import TouchableOpacityBox from '@components/TouchableOpacityBox' @@ -7,7 +6,7 @@ import { PaymentV2 } from '@helium/transactions' import { useMetaplexMetadata } from '@hooks/useMetaplexMetadata' import { PublicKey } from '@solana/web3.js' import BN from 'bn.js' -import React, { memo, useCallback, useRef, useState } from 'react' +import React, { memo, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { LayoutChangeEvent } from 'react-native' import { useAccountStorage } from '../../storage/AccountStorageProvider' @@ -41,29 +40,17 @@ const PaymentCard = ({ const { t } = useTranslation() const [payEnabled, setPayEnabled] = useState(false) const [height, setHeight] = useState(0) - const ledgerPaymentRef = useRef(null) const { currentAccount } = useAccountStorage() const handlePayPressed = useCallback(async () => { - if (!currentAccount?.ledgerDevice) { - const hasSecureAccount = await checkSecureAccount( - currentAccount?.address, - true, - ) - if (!hasSecureAccount) return - animateTransition('PaymentCard.payEnabled') - setPayEnabled(true) - } else { - // is ledger device - ledgerPaymentRef.current?.show({ - payments: payments || [], - ledgerDevice: currentAccount.ledgerDevice, - address: currentAccount.address, - accountIndex: currentAccount.accountIndex || 0, - speculativeNonce: 0, - }) - } - }, [currentAccount, payments]) + const hasSecureAccount = await checkSecureAccount( + currentAccount?.address, + true, + ) + if (!hasSecureAccount) return + animateTransition('PaymentCard.payEnabled') + setPayEnabled(true) + }, [currentAccount]) const handleLayout = useCallback( (e: LayoutChangeEvent) => { diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 32a40fb5f..0a228d237 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -18,7 +18,6 @@ import TokenSelector, { } from '@components/TokenSelector' import TouchableOpacityBox from '@components/TouchableOpacityBox' import Address, { NetTypes } from '@helium/address' -import { Ticker } from '@helium/currency' import { useMint, useOwnedAmount } from '@helium/helium-react-hooks' import { DC_MINT, HNT_MINT } from '@helium/spl-utils' import useDisappear from '@hooks/useDisappear' @@ -87,7 +86,7 @@ const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { if (opts.payments) { return JSON.parse(opts.payments).map((p: LinkedPayment) => ({ ...p, - mint: p.mint || Mints[p.defaultTokenType?.toUpperCase() as Ticker], + mint: p.mint || Mints[p.defaultTokenType?.toUpperCase()], })) } if (opts.payee) { @@ -95,8 +94,7 @@ const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { { payee: opts.payee, amount: opts.amount, - mint: - opts.mint || Mints[opts.defaultTokenType?.toUpperCase() as Ticker], + mint: opts.mint || Mints[opts.defaultTokenType?.toUpperCase()], }, ] } diff --git a/src/features/payment/PaymentTypeSelector.tsx b/src/features/payment/PaymentTypeSelector.tsx deleted file mode 100644 index a4065c530..000000000 --- a/src/features/payment/PaymentTypeSelector.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable react/jsx-props-no-spreading */ -import React, { memo, useCallback, useMemo } from 'react' -import TokenMOBILE from '@assets/images/tokenMOBILE.svg' -import TokenHNT from '@assets/images/tokenHNT.svg' -import { BoxProps } from '@shopify/restyle' -import { Ticker } from '@helium/currency' -import Box from '@components/Box' -import { Theme } from '@theme/theme' -import Text from '@components/Text' -import { useColors } from '@theme/themeHooks' -import TouchableOpacityBox from '@components/TouchableOpacityBox' -import { useAccountStorage } from '../../storage/AccountStorageProvider' -import { accountCurrencyType } from '../../utils/accountUtils' - -type Props = { - onChangeTokenType: (ticker: Ticker) => void - ticker: Ticker -} & BoxProps - -const TokenTypeItem = ({ - ticker, - selected, - onPress, -}: { - ticker: Ticker - selected: boolean - onPress: (ticker: Ticker) => void -}) => { - const { currentAccount } = useAccountStorage() - const colors = useColors() - const color = useCallback( - (isIcon = true) => { - const selectedColor = - ticker === 'MOBILE' && isIcon ? 'blueBright500' : 'primaryText' - return selected ? selectedColor : 'secondaryIcon' - }, - [selected, ticker], - ) - const handlePress = useCallback(() => onPress(ticker), [onPress, ticker]) - - const title = useMemo( - () => accountCurrencyType(currentAccount?.address, ticker).ticker, - [currentAccount, ticker], - ) - - return ( - - {ticker === 'HNT' ? ( - - ) : ( - - )} - - {title} - - {selected && ( - - )} - - ) -} - -const PaymentTypeSelector = ({ - onChangeTokenType, - ticker: tokenType, - ...boxProps -}: Props) => { - return ( - - - - - - - - ) -} - -export default memo(PaymentTypeSelector) diff --git a/src/features/swaps/swapTypes.ts b/src/features/swaps/swapTypes.ts index 716305b5e..2e5d8b7dc 100644 --- a/src/features/swaps/swapTypes.ts +++ b/src/features/swaps/swapTypes.ts @@ -1,4 +1,3 @@ -import { Ticker } from '@helium/currency' import { StackNavigationProp } from '@react-navigation/stack' export type SwapStackParamList = { diff --git a/src/locales/en.ts b/src/locales/en.ts index 4aaaa8184..30af9b86e 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -480,29 +480,6 @@ export default { add: 'Add New Wallet', addTestnet: 'Add New Testnet Wallet', }, - dappLogin: { - account: { - subtitle: 'Which wallet do you want to authenticate with {{appName}}?', - title: 'Choose your\nWallet', - }, - connect: { - continue: 'Continue', - subtitle: 'Authenticate {{appName}}\nwith your Helium Wallet?', - title: 'Connect to {{appName}}?', - }, - error: 'Failed to verify {{appName}}', - ledger: { - subtitle: - 'You must sign burn transaction to login to {{appName}}. Please verify the burn transaction on your Ledger device {{deviceName}}', - title: 'Ledger Approval', - }, - login: 'Login', - timeoutAlert: { - title: 'Login Failed', - message: - 'Please close and reopen the login screen in Crowdspot and scan a new QR code to try again.', - }, - }, editContact: { delete: 'Delete', deleteConfirmMessage: diff --git a/src/navigation/RootNavigator.tsx b/src/navigation/RootNavigator.tsx index b6bdc8e58..285d126e9 100644 --- a/src/navigation/RootNavigator.tsx +++ b/src/navigation/RootNavigator.tsx @@ -24,7 +24,6 @@ import { useAppDispatch } from '../store/store' import LinkWallet from '../features/txnDelegation/LinkWallet' import PaymentScreen from '../features/payment/PaymentScreen' import SignHotspot from '../features/txnDelegation/SignHotspot' -import DappLoginScreen from '../features/dappLogin/DappLoginScreen' import ImportPrivateKey from '../features/onboarding/import/ImportPrivateKey' const screenOptions = { headerShown: false } as StackNavigationOptions @@ -94,11 +93,6 @@ const RootNavigator = () => { component={PaymentScreen} options={screenOptions} /> - > export type BalanceInfo = { atas: Required[] - dcBalance: Balance formattedDcValue: string formattedEscrowDcValue: string formattedHntValue: string @@ -41,13 +32,4 @@ export type BalanceInfo = { formattedMobileValue: string formattedSolValue: string formattedTotal: string - hntBalance: Balance - hntValue: number - iotBalance: Balance - iotValue: number - mobileBalance: Balance - mobileValue: number - solBalance: Balance - solToken: Omit - solValue: number } diff --git a/src/types/solana.ts b/src/types/solana.ts index a74035b12..02d78188f 100644 --- a/src/types/solana.ts +++ b/src/types/solana.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Ticker } from '@helium/currency' import { JsonMetadata, Nft, @@ -42,20 +40,6 @@ export type SolPaymentInfo = { wallet: string } -export const toMintAddress = ( - symbol: string, - mints: Record, -) => { - const ticker = symbol.toUpperCase() as Ticker - return mints[ticker] -} - -export const mintToTicker = (mint: string, mints: Record) => { - const found = Object.keys(mints).find((key) => mints[key as Ticker] === mint) - - return found as Ticker | undefined -} - export type CompressedNFT = { interface: string id: string diff --git a/src/utils/Balance.tsx b/src/utils/Balance.tsx index c544d1461..283ccf159 100644 --- a/src/utils/Balance.tsx +++ b/src/utils/Balance.tsx @@ -1,13 +1,11 @@ -import Balance, { - DataCredits, - IotTokens, - MobileTokens, - NetworkTokens, - SolTokens, - TestNetworkTokens, -} from '@helium/currency' import { getOraclePrice } from '@helium/currency-utils' -import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' +import { + DC_MINT, + HNT_MINT, + IOT_MINT, + MOBILE_MINT, + toNumber, +} from '@helium/spl-utils' import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' import BN from 'bn.js' import React, { @@ -31,8 +29,6 @@ import { syncTokenAccounts } from '../store/slices/balancesSlice' import { useAppDispatch } from '../store/store' import { AccountBalance, BalanceInfo, TokenAccount } from '../types/balance' import StoreAtaBalance from './StoreAtaBalance' -import StoreSolBalance from './StoreSolBalance' -import { decimalSeparator, groupSeparator } from './i18n' import { humanReadable } from './solanaUtils' import { useBalanceHistory } from './useBalanceHistory' import { usePollTokenPrices } from './usePollTokenPrices' @@ -250,17 +246,12 @@ const { Provider } = BalanceContext export const BalanceProvider = ({ children }: { children: ReactNode }) => { const balanceHook = useBalanceHook() - const { atas, solToken } = balanceHook + const { atas } = balanceHook const { cluster } = useSolana() - const prevSolAddress = usePrevious(solToken?.tokenAccount) const prevCluster = usePrevious(cluster) const clusterChanged = prevCluster && cluster && prevCluster !== cluster - const addressChanged = - solToken?.tokenAccount && - prevSolAddress && - solToken.tokenAccount !== prevSolAddress - if (clusterChanged || addressChanged) { + if (clusterChanged) { return <>{children} } @@ -269,9 +260,6 @@ export const BalanceProvider = ({ children }: { children: ReactNode }) => { {atas?.map((ta) => ( ))} - {solToken?.tokenAccount && ( - - )} {children} @@ -279,22 +267,3 @@ export const BalanceProvider = ({ children }: { children: ReactNode }) => { } export const useBalance = () => useContext(BalanceContext) - -export const balanceToString = ( - balance?: Balance< - | DataCredits - | NetworkTokens - | TestNetworkTokens - | MobileTokens - | IotTokens - | SolTokens - >, - opts?: { maxDecimalPlaces?: number; showTicker?: boolean }, -) => { - if (!balance) return '' - return balance.toString(opts?.maxDecimalPlaces, { - groupSeparator, - decimalSeparator, - showTicker: opts?.showTicker, - }) -} diff --git a/src/utils/accountUtils.ts b/src/utils/accountUtils.ts index 3e7c53447..4d0303d16 100644 --- a/src/utils/accountUtils.ts +++ b/src/utils/accountUtils.ts @@ -1,10 +1,9 @@ import Address, { NetTypes as NetType, utils } from '@helium/address' -import { CurrencyType, Ticker } from '@helium/currency' -import Bcrypt from 'bcrypt-react-native' import { PublicKey } from '@solana/web3.js' +import Bcrypt from 'bcrypt-react-native' +import BigNumber from 'bignumber.js' import bs58 from 'bs58' import { round } from 'lodash' -import BigNumber from 'bignumber.js' export type AccountNetTypeOpt = 'all' | NetType.NetType @@ -47,37 +46,6 @@ export const heliumAddressIsValid = (address: string) => { } } -export const accountCurrencyType = (address?: string, tokenType?: Ticker) => { - if (!address) return CurrencyType.default - if (!tokenType) { - return accountNetType(address) === NetType.MAINNET - ? CurrencyType.default - : CurrencyType.testNetworkToken - } - // If token type is passed in, we need to check if to return testnet token or default token - switch (tokenType) { - default: - case 'HNT': - return accountNetType(address) === NetType.MAINNET - ? CurrencyType.default - : CurrencyType.testNetworkToken - case 'HST': - return CurrencyType.security - case 'IOT': - return CurrencyType.iot - case 'MOBILE': - return CurrencyType.mobile - case 'DC': - return CurrencyType.dataCredit - } -} - -export const networkCurrencyType = (netType?: NetType.NetType) => { - return netType === NetType.TESTNET - ? CurrencyType.testNetworkToken - : CurrencyType.default -} - export const accountNetType = (address?: string) => { if (!address || !Address.isValid(address)) return NetType.MAINNET return Address.fromB58(address)?.netType diff --git a/src/utils/linking.ts b/src/utils/linking.ts index 3ad052907..0d9f913a6 100644 --- a/src/utils/linking.ts +++ b/src/utils/linking.ts @@ -1,5 +1,4 @@ import Address from '@helium/address' -import Balance, { CurrencyType } from '@helium/currency' import { LinkingOptions } from '@react-navigation/native' import BigNumber from 'bignumber.js' import BN from 'bn.js' @@ -27,7 +26,6 @@ export const authenticatedLinking: LinkingOptions = { LinkWallet: 'link_wallet', SignHotspot: 'sign_hotspot', PaymentScreen: 'payment', - DappLoginScreen: 'dapp_login', ImportPrivateKey: 'import_key/:key', }, }, @@ -133,8 +131,6 @@ export const parsePaymentLink = ( return } - const { coefficient } = new Balance(0, CurrencyType.networkToken).type - if (parsedJson.amount !== undefined) { const amount = typeof parsedJson.amount === 'string' @@ -143,7 +139,7 @@ export const parsePaymentLink = ( return { payee: parsedJson.address || parsedJson.payee, payer: parsedJson.payer, - amount: new BigNumber(amount).dividedBy(coefficient).toString(), + amount: new BigNumber(amount).dividedBy(10 ** 8).toString(), memo: '', } } @@ -155,7 +151,7 @@ export const parsePaymentLink = ( const amountFloat = typeof amount === 'string' ? parseFloat(amount) : amount return { - amount: new BigNumber(amountFloat).dividedBy(coefficient).toString(), + amount: new BigNumber(amountFloat).dividedBy(10 ** 8).toString(), payee: address, memo: '', } diff --git a/yarn.lock b/yarn.lock index 213648d2a..ebf354db1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2259,19 +2259,15 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@helium/account-fetch-cache-hooks@^0.2.16": +"@helium/account-fetch-cache-hooks@^0.2.16", "@helium/account-fetch-cache-hooks@file:.yalc/@helium/account-fetch-cache-hooks": version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache-hooks/-/account-fetch-cache-hooks-0.2.16.tgz#b085baa6c959207e3dcee306d2f9cedf9c3db178" - integrity sha512-5gO11VZuVrP22U2Gtdy46V2iSq+sLWWnLVj6mS1iYMxXFb/Fox0hmv3JkRVR3Ax4UXRrd0iWrI1KBy2DaNqU6Q== dependencies: "@helium/account-fetch-cache" "^0.2.16" "@solana/web3.js" "^1.66.2" react-async-hook "^4.0.0" -"@helium/account-fetch-cache@^0.2.16": +"@helium/account-fetch-cache@^0.2.16", "@helium/account-fetch-cache@file:.yalc/@helium/account-fetch-cache": version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.16.tgz#2283f1511279622354a3a6b53b5e573e8a820cfa" - integrity sha512-GEHD1xjYMWTLJOr4TEdrczu/J7LMusEcmh1qc+ZZ8QSHPm7xjNG8ELVTdUiL8f6Sg3EKxezErCUFz/7u2cc1Sg== dependencies: "@solana/web3.js" "^1.43.4" @@ -2352,13 +2348,6 @@ "@solana/spl-token" "^0.3.7" "@solana/web3.js" "^1.73.0" -"@helium/currency@4.11.1": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@helium/currency/-/currency-4.11.1.tgz#4658037197638406f0e60b246db70acd3323e46b" - integrity sha512-WxsMm3TvIXV8itR36yYxjXjtWAgJlId/OOrHivNTXUIdnxi6/j3JO/hY7EUHYygcw57O90Zqg4EBnypXzI4EKw== - dependencies: - bignumber.js "^9.0.0" - "@helium/currency@^4.7.3": version "4.10.0" resolved "https://registry.yarnpkg.com/@helium/currency/-/currency-4.10.0.tgz#c2cdd385c1c810d8664c1c3f88e888f802e299d3" From 9bb521c17d8a041e88938c036db16ba29a08366a Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 3 Aug 2023 17:58:55 -0500 Subject: [PATCH 12/23] More efficient calls --- package.json | 2 +- src/App.tsx | 14 +-- src/features/account/useTxn.tsx | 6 +- src/features/payment/PaymentScreen.tsx | 8 +- src/hooks/useMetaplexMetadata.ts | 1 + src/hooks/useSimulatedTransaction.ts | 14 +-- src/hooks/useSolanaHealth.ts | 9 +- src/solana/SolanaProvider.tsx | 121 +++++++++++-------------- src/utils/solanaUtils.ts | 23 +++-- 9 files changed, 90 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index d4e0a3052..4a1cf6304 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@helium/transactions": "4.8.1", "@helium/treasury-management-sdk": "0.1.2", "@helium/voter-stake-registry-sdk": "0.1.2", - "@helium/wallet-link": "4.10.0", + "@heliurm/wallet-link": "4.10.0", "@ledgerhq/hw-app-helium": "6.29.1", "@ledgerhq/react-native-hid": "6.28.4", "@ledgerhq/react-native-hw-transport-ble": "6.27.2", diff --git a/src/App.tsx b/src/App.tsx index 794e5253f..aca2c8d8f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' import { PortalHost, PortalProvider } from '@gorhom/portal' -import { AccountProvider } from '@helium/account-fetch-cache-hooks' +import { AccountContext } from '@helium/account-fetch-cache-hooks' import { DarkTheme, NavigationContainer } from '@react-navigation/native' import MapboxGL from '@rnmapbox/maps' import { ThemeProvider } from '@shopify/restyle' @@ -55,7 +55,7 @@ const App = () => { const { appState } = useAppState() const { restored: accountsRestored } = useAccountStorage() - const { connection } = useSolana() + const { cache } = useSolana() const { setOpenedNotification } = useNotificationStorage() const linking = useDeepLinking() @@ -112,13 +112,9 @@ const App = () => { - {connection && ( + {cache && ( - + {accountsRestored && ( <> { /> )} - + )} diff --git a/src/features/account/useTxn.tsx b/src/features/account/useTxn.tsx index 46684810d..55715cc4c 100644 --- a/src/features/account/useTxn.tsx +++ b/src/features/account/useTxn.tsx @@ -55,7 +55,11 @@ const useTxn = ( [mintKeys], ) const { accounts: mintAccs } = useAccounts(mintKeys, MintParser) - const { accounts: metadataAccs } = useAccounts(metadataKeys, METADATA_PARSER) + const { accounts: metadataAccs } = useAccounts( + metadataKeys, + METADATA_PARSER, + true, + ) const decimalsByMint = useMemo(() => { return mintAccs?.reduce((acc, curr) => { if (curr.info) { diff --git a/src/features/payment/PaymentScreen.tsx b/src/features/payment/PaymentScreen.tsx index 0a228d237..9e1f6b677 100644 --- a/src/features/payment/PaymentScreen.tsx +++ b/src/features/payment/PaymentScreen.tsx @@ -86,7 +86,9 @@ const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { if (opts.payments) { return JSON.parse(opts.payments).map((p: LinkedPayment) => ({ ...p, - mint: p.mint || Mints[p.defaultTokenType?.toUpperCase()], + mint: + p.mint || + (p.defaultTokenType && Mints[p.defaultTokenType.toUpperCase()]), })) } if (opts.payee) { @@ -94,7 +96,9 @@ const parseLinkedPayments = (opts: PaymentRouteParam): LinkedPayment[] => { { payee: opts.payee, amount: opts.amount, - mint: opts.mint || Mints[opts.defaultTokenType?.toUpperCase()], + mint: + opts.mint || + (opts.defaultTokenType && Mints[opts.defaultTokenType.toUpperCase()]), }, ] } diff --git a/src/hooks/useMetaplexMetadata.ts b/src/hooks/useMetaplexMetadata.ts index 53434b5a7..9e69e9185 100644 --- a/src/hooks/useMetaplexMetadata.ts +++ b/src/hooks/useMetaplexMetadata.ts @@ -66,6 +66,7 @@ export function useMetaplexMetadata(mint: PublicKey | undefined): { const { info: metadataAcc, loading } = useAccount( metadataAddr, METADATA_PARSER, + true, ) const { result: json, loading: jsonLoading } = useAsync(getMetadata, [ metadataAcc?.uri, diff --git a/src/hooks/useSimulatedTransaction.ts b/src/hooks/useSimulatedTransaction.ts index 4d95d1ca1..7f953d0b2 100644 --- a/src/hooks/useSimulatedTransaction.ts +++ b/src/hooks/useSimulatedTransaction.ts @@ -1,6 +1,5 @@ import { toNumber, truthy, sendAndConfirmWithRetry } from '@helium/spl-utils' import useAlert from '@hooks/useAlert' -import { Metaplex } from '@metaplex-foundation/js' import { AccountLayout, NATIVE_MINT, TOKEN_PROGRAM_ID } from '@solana/spl-token' import { AddressLookupTableAccount, @@ -43,7 +42,7 @@ export function useSimulatedTransaction( ): SimulatedTransactionResult { const { showOKCancelAlert } = useAlert() const { tokenAccounts } = useBalance() - const { connection, anchorProvider, cluster } = useSolana() + const { connection, anchorProvider } = useSolana() const { t: tr } = useTranslation() const { hntSolConvertTransaction, hntEstimate, hasEnoughSol } = useHntSolConvert() @@ -51,13 +50,6 @@ export function useSimulatedTransaction( const [simulationError, setSimulationError] = useState(false) const [insufficientFunds, setInsufficientFunds] = useState(false) - const metaplex = useMemo(() => { - if (!connection || !cluster) return - return new Metaplex(connection, { - cluster, - }) - }, [connection, cluster]) - const transaction = useMemo(() => { if (!serializedTx) return undefined try { @@ -168,7 +160,6 @@ export function useSimulatedTransaction( !connection || !transaction || !anchorProvider || - !metaplex || !wallet || !simulationAccounts || !tokenAccounts @@ -293,7 +284,7 @@ export function useSimulatedTransaction( const tokenMetadata = await getCollectableByMint( tokenMint, - metaplex, + connection, ) const type = accountNativeBalance.lt(existingNativeBalance) @@ -342,7 +333,6 @@ export function useSimulatedTransaction( transaction, tokenAccounts, hasEnoughSol, - metaplex, anchorProvider, wallet, ]) diff --git a/src/hooks/useSolanaHealth.ts b/src/hooks/useSolanaHealth.ts index 6121541dd..49f9490ba 100644 --- a/src/hooks/useSolanaHealth.ts +++ b/src/hooks/useSolanaHealth.ts @@ -14,18 +14,17 @@ function doTimeout() { lastUpdated = new Date().valueOf() } } -let cachedHealthByEndpoint: Record = {} +let cachedHealthByEndpoint: Record> = {} async function getCachedHealth(connection: WrappedConnection): Promise { doTimeout() if (!cachedHealthByEndpoint[connection.rpcEndpoint]) { - const { result: health } = await connection.getHealth() - cachedHealthByEndpoint[connection.rpcEndpoint] = health + cachedHealthByEndpoint[connection.rpcEndpoint] = connection.getHealth() } - return cachedHealthByEndpoint[connection.rpcEndpoint] + return (await cachedHealthByEndpoint[connection.rpcEndpoint]).result } -let cachedTps: Record = {} +let cachedTps: Record> = {} async function getCachedTPS(connection: WrappedConnection): Promise { doTimeout() if (!cachedTps[connection.rpcEndpoint]) { diff --git a/src/solana/SolanaProvider.tsx b/src/solana/SolanaProvider.tsx index 6945fd653..92cdae432 100644 --- a/src/solana/SolanaProvider.tsx +++ b/src/solana/SolanaProvider.tsx @@ -12,19 +12,18 @@ import React, { useCallback, useContext, useEffect, - useRef, + useMemo, useState, } from 'react' +import { useAsync } from 'react-async-hook' import Config from 'react-native-config' import { useSelector } from 'react-redux' -import usePrevious from '../hooks/usePrevious' import { useAccountStorage } from '../storage/AccountStorageProvider' import { getSessionKey, getSolanaKeypair } from '../storage/secureStorage' import { RootState } from '../store/rootReducer' import { appSlice } from '../store/slices/appSlice' import { useAppDispatch } from '../store/store' import { DcProgram, HemProgram, HsdProgram, LazyProgram } from '../types/solana' -import { WrappedConnection } from '../utils/WrappedConnection' import { getConnection } from '../utils/solanaUtils' const useSolanaHook = () => { @@ -33,50 +32,34 @@ const useSolanaHook = () => { const cluster = useSelector( (state: RootState) => state.app.cluster || 'mainnet-beta', ) - const [connection, setConnection] = useState() const [dcProgram, setDcProgram] = useState() const [hemProgram, setHemProgram] = useState() const [hsdProgram, setHsdProgram] = useState() const [lazyProgram, setLazyProgram] = useState() - const [anchorProvider, setAnchorProvider] = useState() - const [cache, setCache] = useState() + const { loading, result: sessionKey } = useAsync(getSessionKey, []) + const connection = useMemo(() => { + const sessionKeyActual = + !loading && !sessionKey ? Config.RPC_SESSION_KEY_FALLBACK : sessionKey - const initialized = useRef(false) - const prevAddress = usePrevious(currentAccount?.address) - const prevCluster = usePrevious(cluster) - - const handleConnectionChanged = useCallback(async () => { - if (!cluster) return - - const sessionKey = - (await getSessionKey()) || Config.RPC_SESSION_KEY_FALLBACK - const nextConn = getConnection(cluster, sessionKey) - - if (!currentAccount?.address) { - setConnection(nextConn) - return - } - - if ( - initialized.current && - prevAddress === currentAccount.address && - prevCluster === cluster - ) { - return + if (sessionKeyActual) { + return getConnection(cluster, sessionKeyActual) } + }, [cluster, sessionKey, loading]) + const address = useMemo( + () => currentAccount?.address, + [currentAccount?.address], + ) + const { result: secureAcct } = useAsync( + async (addr: string | undefined) => { + if (addr) { + return getSolanaKeypair(addr) + } + }, + [address], + ) - initialized.current = true - - if ( - nextConn.baseURL === connection?.baseURL && - prevAddress === currentAccount.address - ) - return - - setConnection(nextConn) - - const secureAcct = await getSolanaKeypair(currentAccount.address) - if (!secureAcct) return + const anchorProvider = useMemo(() => { + if (!secureAcct || !connection) return const anchorWallet = { signTransaction: async (transaction: Transaction) => { @@ -93,47 +76,43 @@ const useSolanaHook = () => { return secureAcct?.publicKey }, } as Wallet - - const nextProvider = new AnchorProvider(nextConn, anchorWallet, { + return new AnchorProvider(connection, anchorWallet, { preflightCommitment: 'confirmed', commitment: 'confirmed', }) + }, [connection, secureAcct]) + + const cache = useMemo(() => { + if (!connection) return - setAnchorProvider(nextProvider) - initHem(nextProvider).then(setHemProgram) - initHsd(nextProvider).then(setHsdProgram) - initDc(nextProvider).then(setDcProgram) - initLazy(nextProvider).then(setLazyProgram) - - cache?.close() - setCache( - new AccountFetchCache({ - connection: nextConn, - delay: 100, - commitment: 'confirmed', - missingRefetchDelay: 60 * 1000, - extendConnection: true, - }), - ) + return new AccountFetchCache({ + connection, + delay: 100, + commitment: 'confirmed', + missingRefetchDelay: 60 * 1000, + extendConnection: true, + }) + }, [connection]) + useEffect(() => { // Don't sub to hnt or dc they change a bunch cache?.statics.add(HNT_MINT.toBase58()) cache?.statics.add(DC_MINT.toBase58()) - return () => { - cache?.close() - } - }, [ - cache, - cluster, - connection?.baseURL, - currentAccount, - prevAddress, - prevCluster, - ]) + return () => cache?.close() + }, [cache]) + + const handleConnectionChanged = useCallback(async () => { + if (!anchorProvider) return + + initHem(anchorProvider).then(setHemProgram) + initHsd(anchorProvider).then(setHsdProgram) + initDc(anchorProvider).then(setDcProgram) + initLazy(anchorProvider).then(setLazyProgram) + }, [anchorProvider]) useEffect(() => { handleConnectionChanged() - }, [cluster, currentAccount, handleConnectionChanged]) + }, [cluster, address, handleConnectionChanged]) const updateCluster = useCallback( (nextCluster: Cluster) => { @@ -151,6 +130,7 @@ const useSolanaHook = () => { hsdProgram, lazyProgram, updateCluster, + cache, } } @@ -162,6 +142,7 @@ const initialState = { hemProgram: undefined, hsdProgram: undefined, lazyProgram: undefined, + cache: undefined, updateCluster: (_nextCluster: Cluster) => {}, } const SolanaContext = diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index a4eb9883d..6a943279a 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -46,6 +46,7 @@ import { registrarCollectionKey, registrarKey, } from '@helium/voter-stake-registry-sdk' +import { METADATA_PARSER, getMetadataId } from '@hooks/useMetaplexMetadata' import { JsonMetadata, Metadata, Metaplex } from '@metaplex-foundation/js' import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, @@ -1179,15 +1180,22 @@ export const groupCollectablesWithMetaData = ( */ export const getCollectableByMint = async ( mint: PublicKey, - metaplex: Metaplex, + connection: Connection, ): Promise => { try { - const collectable = await metaplex.nfts().findByMint({ mintAddress: mint }) - if (!collectable.json && collectable.uri) { - const json = await (await fetch(collectable.uri)).json() - return { ...collectable, json } + const metadata = getMetadataId(mint) + const metadataAccount = await connection.getAccountInfo(metadata) + if (metadataAccount) { + const collectable = METADATA_PARSER(metadata, metadataAccount) + if (!collectable.json && collectable.uri) { + const json = await (await fetch(collectable.uri)).json() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return { ...collectable, json } + } } - return collectable + + return null } catch (e) { return null } @@ -1208,7 +1216,6 @@ export const getAllTransactions = async ( ): Promise<(EnrichedTransaction | ConfirmedSignatureInfo)[]> => { const pubKey = new PublicKey(address) const conn = anchorProvider.connection - const metaplex = new Metaplex(conn, { cluster }) const sessionKey = await getSessionKey() const parseTransactionsUrl = `${ @@ -1239,7 +1246,7 @@ export const getAllTransactions = async ( if (firstTokenTransfer && firstTokenTransfer.mint) { const tokenMetadata = await getCollectableByMint( new PublicKey(firstTokenTransfer.mint), - metaplex, + conn, ) return { From e01499c588c6530edc866e9f6f0bdb0ddefda10d Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 3 Aug 2023 18:02:20 -0500 Subject: [PATCH 13/23] Bugfix --- src/features/account/useTxn.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/account/useTxn.tsx b/src/features/account/useTxn.tsx index 55715cc4c..b5c131fb5 100644 --- a/src/features/account/useTxn.tsx +++ b/src/features/account/useTxn.tsx @@ -71,7 +71,7 @@ const useTxn = ( const symbolsByMint = useMemo(() => { return metadataAccs?.reduce((acc, curr, index) => { - if (curr.info) { + if (curr.info && mintKeys[index]) { acc[mintKeys[index].toBase58()] = curr.info.symbol } return acc From 654e88e4f2d53553b8b3aa216a8e2e5c110d3d8d Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Fri, 4 Aug 2023 10:55:56 -0500 Subject: [PATCH 14/23] Use published packagges --- package.json | 26 +++---- yarn.lock | 194 ++++++++++++++++++++++++++------------------------- 2 files changed, 113 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index 4a1cf6304..3729c6e4d 100644 --- a/package.json +++ b/package.json @@ -39,29 +39,29 @@ "@coral-xyz/anchor": "0.26.0", "@gorhom/bottom-sheet": "4.4.6", "@gorhom/portal": "1.0.14", - "@helium/account-fetch-cache": "file:.yalc/@helium/account-fetch-cache", - "@helium/account-fetch-cache-hooks": "file:.yalc/@helium/account-fetch-cache-hooks", + "@helium/account-fetch-cache": "^0.2.17", + "@helium/account-fetch-cache-hooks": "^0.2.17", "@helium/address": "4.6.2", - "@helium/circuit-breaker-sdk": "^0.2.16", + "@helium/circuit-breaker-sdk": "^0.2.17", "@helium/crypto-react-native": "4.8.0", "@helium/currency-utils": "0.1.1", - "@helium/data-credits-sdk": "0.2.16", - "@helium/distributor-oracle": "^0.2.16", - "@helium/fanout-sdk": "^0.2.16", - "@helium/helium-entity-manager-sdk": "^0.2.16", - "@helium/helium-react-hooks": "file:.yalc/@helium/helium-react-hooks", - "@helium/helium-sub-daos-sdk": "0.2.16", + "@helium/data-credits-sdk": "^0.2.17", + "@helium/distributor-oracle": "^0.2.17", + "@helium/fanout-sdk": "^0.2.17", + "@helium/helium-entity-manager-sdk": "^0.2.17", + "@helium/helium-react-hooks": "^0.2.17", + "@helium/helium-sub-daos-sdk": "^0.2.17", "@helium/http": "4.7.5", - "@helium/idls": "^0.2.16", - "@helium/lazy-distributor-sdk": "^0.2.16", + "@helium/idls": "^0.2.17", + "@helium/lazy-distributor-sdk": "^0.2.17", "@helium/onboarding": "4.9.0", "@helium/proto-ble": "4.0.0", "@helium/react-native-sdk": "1.0.0", - "@helium/spl-utils": "^0.2.16", + "@helium/spl-utils": "^0.2.17", "@helium/transactions": "4.8.1", "@helium/treasury-management-sdk": "0.1.2", "@helium/voter-stake-registry-sdk": "0.1.2", - "@heliurm/wallet-link": "4.10.0", + "@helium/wallet-link": "4.10.0", "@ledgerhq/hw-app-helium": "6.29.1", "@ledgerhq/react-native-hid": "6.28.4", "@ledgerhq/react-native-hw-transport-ble": "6.27.2", diff --git a/yarn.lock b/yarn.lock index ebf354db1..94278db60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2259,15 +2259,19 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@helium/account-fetch-cache-hooks@^0.2.16", "@helium/account-fetch-cache-hooks@file:.yalc/@helium/account-fetch-cache-hooks": - version "0.2.16" +"@helium/account-fetch-cache-hooks@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache-hooks/-/account-fetch-cache-hooks-0.2.17.tgz#78076508bc2d825022db1b6befd43de378f32bb6" + integrity sha512-A0DHHFLlF1B5EyWLHzLs6hZtax7V5LD48XM0xEYDIIJHkPkUs+gFQCGZyki3NjJpQ/t3RfEpLpmXchP0Yr/OLQ== dependencies: - "@helium/account-fetch-cache" "^0.2.16" + "@helium/account-fetch-cache" "^0.2.17" "@solana/web3.js" "^1.66.2" react-async-hook "^4.0.0" -"@helium/account-fetch-cache@^0.2.16", "@helium/account-fetch-cache@file:.yalc/@helium/account-fetch-cache": - version "0.2.16" +"@helium/account-fetch-cache@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/account-fetch-cache/-/account-fetch-cache-0.2.17.tgz#e016ab0447666bab68fa6669101a34bf89b6feb2" + integrity sha512-N7iyyTMounGLsMaLkxxgc7UWEnwy7Ja9CiAhebF4QDB3DWzO+0YVSM5hCYaPexzgFfewqRiJvmczt3YMXxWERA== dependencies: "@solana/web3.js" "^1.43.4" @@ -2298,10 +2302,10 @@ js-sha256 "^0.9.0" multiformats "^9.6.4" -"@helium/anchor-resolvers@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/anchor-resolvers/-/anchor-resolvers-0.2.16.tgz#4a5deb1799a79ca8525d2a8e5262b4c2b6d3485a" - integrity sha512-mKDuvUJHTDll7Jm7U8VgHnIDmQIsfIqjEXVkQ9mZ8/fOZK3EhyYk9Ws7u4EvwphNuyYugqobyC43XmRaudxRfA== +"@helium/anchor-resolvers@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/anchor-resolvers/-/anchor-resolvers-0.2.17.tgz#662fe995d2c9112f214698bfff2bdd0cf3510b75" + integrity sha512-Fd9WzdEbpRBiYc9uBUNlLu/BS2rNCn2qCdEC1bpZTq8tNAzkOM6KU3mBt4GvY3t1A5NVJjpW2H4sLHz9Uu0JiA== dependencies: "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.43.4" @@ -2318,14 +2322,14 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/circuit-breaker-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.16.tgz#17099fd12473132965ba211c8995403bd7744a43" - integrity sha512-iniL/gVIfaZ16z9NZjpwGGEsRJpyKxkBJmUrfG2inLumoX/A/K3YjHOIdhGTaEJ3V6NNgyyBp2xB5ex8o2blqA== +"@helium/circuit-breaker-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/circuit-breaker-sdk/-/circuit-breaker-sdk-0.2.17.tgz#79baa8778bec3bde091d6189eb03004e553e7da0" + integrity sha512-QZ2k6QeI5hNApcdXO835vcphdEnxZADkEGFGnjNCSkffRQOx5iPdR8EDSAfmYeUHFH917I57qxirRyCE5RPqBw== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/idls" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/idls" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2355,35 +2359,35 @@ dependencies: bignumber.js "^9.0.0" -"@helium/data-credits-sdk@0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/data-credits-sdk/-/data-credits-sdk-0.2.16.tgz#53dbc4148118d68ec0eb714fdefb9ef007d47a12" - integrity sha512-Oox6Mt165yw8k9YpT7Xelk3xNbhUaJlGJY2C+uxCCoSrFUa/XEFXZ4UKvOjoYX/Uw+qi/6jEMhwfqXnXU5nJLA== +"@helium/data-credits-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/data-credits-sdk/-/data-credits-sdk-0.2.17.tgz#778bf5b0c19237ff02b8973f4ab2d6a0fc4b64db" + integrity sha512-6n9quQRnbuCmv2OHkpbALWt2oMUtUnFJhj8m3mLbodeaed+/WDQnStufiK2RhiRv35grKQRxEmz08rbnoM04fA== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/circuit-breaker-sdk" "^0.2.16" - "@helium/helium-sub-daos-sdk" "^0.2.16" - "@helium/idls" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/circuit-breaker-sdk" "^0.2.17" + "@helium/helium-sub-daos-sdk" "^0.2.17" + "@helium/idls" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" -"@helium/distributor-oracle@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/distributor-oracle/-/distributor-oracle-0.2.16.tgz#7c56cdf57c2e3c1c0825eafd7025fc572f210bc8" - integrity sha512-522MooHz5HqgkWB7Iluy00pqzWK+JLiyAuJdsp3hjMlgHr/pgVKI1sMkwPEgyBneTgC7lt//Z9sADuenHpmgZg== +"@helium/distributor-oracle@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/distributor-oracle/-/distributor-oracle-0.2.17.tgz#1c0449ff5f6c7e3d401ceab18f08f9c35fc85ce4" + integrity sha512-okZvhpWmogcUZwstwRLDu16qLyf8DLFYbOfnCniv6bj/SVnVqz2P2sYVE5ktU3xtuCZb8gXX9gebv/hlRuU8Rw== dependencies: "@coral-xyz/anchor" "^0.26.0" "@fastify/cors" "^8.1.1" - "@helium/account-fetch-cache" "^0.2.16" + "@helium/account-fetch-cache" "^0.2.17" "@helium/address" "^4.10.2" - "@helium/helium-entity-manager-sdk" "^0.2.16" - "@helium/helium-sub-daos-sdk" "^0.2.16" - "@helium/idls" "^0.2.16" - "@helium/lazy-distributor-sdk" "^0.2.16" - "@helium/rewards-oracle-sdk" "^0.2.16" - "@helium/spl-utils" "^0.2.16" + "@helium/helium-entity-manager-sdk" "^0.2.17" + "@helium/helium-sub-daos-sdk" "^0.2.17" + "@helium/idls" "^0.2.17" + "@helium/lazy-distributor-sdk" "^0.2.17" + "@helium/rewards-oracle-sdk" "^0.2.17" + "@helium/spl-utils" "^0.2.17" "@metaplex-foundation/mpl-bubblegum" "^0.7.0" "@solana/spl-token" "^0.3.8" "@types/sequelize" "^4.28.14" @@ -2399,55 +2403,57 @@ sequelize "^6.28.0" typescript-collections "^1.3.3" -"@helium/fanout-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/fanout-sdk/-/fanout-sdk-0.2.16.tgz#72beb66a47089a28b68ee849ce932b1a252f7f9d" - integrity sha512-a/81yhGXSsYcEMSaP7MOQDMxoU117J5UUzUj0Zindmn0pXyF2VpahOqZY4KYiJdatbr7LpAuRfwZYdKVuQP8TA== +"@helium/fanout-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/fanout-sdk/-/fanout-sdk-0.2.17.tgz#62f252bcf3a733650b06853174dd9aa21305c922" + integrity sha512-ZS/0tOT3Xd6g5ZsV1/3NYaIWfP1+HTD4sqgYwohFOsAd/1ufBPMeaqv1DD04AMK+wW6cZXriFNOx8orydMbeWA== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/idls" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/idls" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/helium-entity-manager-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.2.16.tgz#c4ad10ed7546d34fe0819f63a2837b2bd2ce14b1" - integrity sha512-abS86eXoII2zNQDp8UZZPDkYu2NrwOY+/KMTVceuAVtDqhLslltDYC4ToV/bS1GCETjm+VJLZcgt0lXIeFfuJA== +"@helium/helium-entity-manager-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/helium-entity-manager-sdk/-/helium-entity-manager-sdk-0.2.17.tgz#819ea9eaecf8c6c69218cb0e7be4daf5cfd98cb0" + integrity sha512-xDxP4twLo9LaNO85m1jvm8KZx29RNFPMqK7/RtWS0PAVURgIH5t5/85aqge8/dmanzpMMuEySX5Gs66BlUVqAQ== dependencies: "@coral-xyz/anchor" "^0.26.0" "@helium/address" "^4.10.2" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/helium-sub-daos-sdk" "^0.2.16" - "@helium/idls" "^0.2.16" - "@helium/spl-utils" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/helium-sub-daos-sdk" "^0.2.17" + "@helium/idls" "^0.2.17" + "@helium/spl-utils" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" crypto-js "^4.1.1" js-sha256 "^0.9.0" -"@helium/helium-react-hooks@file:.yalc/@helium/helium-react-hooks": - version "0.2.16" +"@helium/helium-react-hooks@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/helium-react-hooks/-/helium-react-hooks-0.2.17.tgz#19b9c13daa57f5c9486fdd1b34e6e9cce26e8ce2" + integrity sha512-x7af6grDch8khPuZc+kLfGWKF9rhYjaRazyRoueFK2qbdezF70Y6SdW0EtF5acS5DubKZjCz/51DBD8CSUXKBQ== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/account-fetch-cache" "^0.2.16" - "@helium/account-fetch-cache-hooks" "^0.2.16" + "@helium/account-fetch-cache" "^0.2.17" + "@helium/account-fetch-cache-hooks" "^0.2.17" "@solana/spl-token" "^0.3.6" "@solana/web3.js" "^1.66.2" bs58 "^5.0.0" pako "^2.0.3" react-async-hook "^4.0.0" -"@helium/helium-sub-daos-sdk@0.2.16", "@helium/helium-sub-daos-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.2.16.tgz#4ad6eb45b1bb28773f9695a6fadd337338bb586e" - integrity sha512-goa7XVEkUSCqLT+eihRFrsTzUAjiH9ybWv8HPEvuxB/xbLXp/ACqe4clldqLwT4DWTTr9aCkfc3G7ptSO6lgoQ== +"@helium/helium-sub-daos-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/helium-sub-daos-sdk/-/helium-sub-daos-sdk-0.2.17.tgz#dbd279eceaff152791cea316495cc51c1d275391" + integrity sha512-P7q3wLVdpsvq5eJpWy1BdCy6amQjkz1ZEa/aIPTKNvuGPIjJjRzARzP0Aht4119JjvlMYd8tFsZYCqrDUnoiXA== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/circuit-breaker-sdk" "^0.2.16" - "@helium/treasury-management-sdk" "^0.2.16" - "@helium/voter-stake-registry-sdk" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/circuit-breaker-sdk" "^0.2.17" + "@helium/treasury-management-sdk" "^0.2.17" + "@helium/voter-stake-registry-sdk" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2486,10 +2492,10 @@ borsh "^0.7.0" bs58 "^4.0.1" -"@helium/idls@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.2.16.tgz#908fbceb1f8a634235f0ec98fd23c6e022a43ab5" - integrity sha512-c1gp1PQ9+m832UpoOTo1SmcbqJ4pcMARQMklkFJx27l0iyHkL1zI4IusyXWH9yoKIgSklnlPKEm67b3LYd1Nsg== +"@helium/idls@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/idls/-/idls-0.2.17.tgz#d9908804df406a2e2953fc1f2038b3fbd62d4d41" + integrity sha512-/RUreFrmAEeis2+b29sgMPfmfGU5VL1z8N9B4NzB+iRkDCxMa3nXn0HRKdNGj0nmawpZlQhG90uOz/NKlluwjQ== dependencies: "@coral-xyz/anchor" "^0.26.0" "@solana/web3.js" "^1.43.4" @@ -2497,14 +2503,14 @@ borsh "^0.7.0" bs58 "^4.0.1" -"@helium/lazy-distributor-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.2.16.tgz#c3c3cf589cd16e7e2a3b9a3316b0e25b54c1db79" - integrity sha512-JMrITdyLYGDx52GBPhleDEg13X6p08rCl2GoRjCYmXVhS3rgmnjVtFRVPYyqq1Rh+4O5yGq1h9TO4exyCSguHA== +"@helium/lazy-distributor-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/lazy-distributor-sdk/-/lazy-distributor-sdk-0.2.17.tgz#4c2b0ecb89b92d9c2084ff9c04e365a275c50d61" + integrity sha512-FgdeV9msUyHGuNfofohc9nj9ioryRVQfMDJAEzFx4qxoWTKkcbQJKGy578JZR3ci0QlARayEBJUf+I5JljK07Q== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/circuit-breaker-sdk" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/circuit-breaker-sdk" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2536,13 +2542,13 @@ resolved "https://registry.yarnpkg.com/@helium/react-native-sdk/-/react-native-sdk-1.0.0.tgz#41024fa99859490bd8a0b717f52acc11ae72f114" integrity sha512-Qi1Nnp/q2hsz2D7aeuM6LxXhNX8NrHz1U+PoQslwK2XfqPFZEYb4uAzjXDKlc+JBWPiF96GMJywv/ofxlZ9XLg== -"@helium/rewards-oracle-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.2.16.tgz#4c0a27b4ac755e041ada9d4cb9ceba4bb1a0b897" - integrity sha512-IdI60FiBYncjYG5FLAuakD/75EN8EZHowBZZ9yBrjxsFe7fc1cShpme+GTK1nQBhXkZlRyU1dcyyWgeICdutgA== +"@helium/rewards-oracle-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/rewards-oracle-sdk/-/rewards-oracle-sdk-0.2.17.tgz#a356d1407c06862a382ecfa0889bf37667ee33e8" + integrity sha512-Uq1DcmdcfkrpQ839KQeBv38hMmaeHm33Iq8Nz8ajah/YbqYMgIsvgvr0999+t37T/yn8xvjJGpVm3rHAjPgEwg== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" "@helium/idls" "^0.0.43" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2561,15 +2567,15 @@ borsh "^0.7.0" bs58 "^5.0.0" -"@helium/spl-utils@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.16.tgz#3442534dd8121557bf144f1e6fdb74c11361099b" - integrity sha512-pwSqP1XDX4CMOdW/Wycyji2MvnDO9UWoBjFW5vConV/FNeXu0PGb1q8ZegpBRlQVFxxSxkq76jwAAZbHub6X+w== +"@helium/spl-utils@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/spl-utils/-/spl-utils-0.2.17.tgz#150a7942039a844a3c11368fc5f93002db7dd708" + integrity sha512-m+IGp4Un9p6XDu/4tgox7guoVbiiyj0LJ9qSPsDaAF1LAVq8N+uzgkacElotHF1d0ePjN8Ku+Q9IEM9+xyCjeg== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/account-fetch-cache" "^0.2.16" + "@helium/account-fetch-cache" "^0.2.17" "@helium/address" "^4.10.2" - "@helium/anchor-resolvers" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" "@metaplex-foundation/mpl-token-metadata" "^2.5.2" "@solana/spl-account-compression" "^0.1.7" "@solana/spl-token" "^0.3.6" @@ -2602,15 +2608,15 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/treasury-management-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.16.tgz#990963dc04694ebce487586eee398f672b34507b" - integrity sha512-6rm8Xdew6hfkOl6wubsNxMxDX1EXbEC/hQQagEg69WwxJHJwiQSw1Qd0Oh3Si0mqbmuXDb99aTEyD1d1sXGNwg== +"@helium/treasury-management-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/treasury-management-sdk/-/treasury-management-sdk-0.2.17.tgz#b5495352e948aa576ea2a24d68406d921c37079f" + integrity sha512-me5Ni6HrCRDRho8IkdUE29zD83wi8UwzLdZpVBIjdrmXVJ9gKLSTkOxVtIq1/nYFJc7IYy3fkXUbOdMs9rt7lA== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/circuit-breaker-sdk" "^0.2.16" - "@helium/idls" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/circuit-breaker-sdk" "^0.2.17" + "@helium/idls" "^0.2.17" bn.js "^5.2.0" bs58 "^4.0.1" @@ -2627,14 +2633,14 @@ bn.js "^5.2.0" bs58 "^4.0.1" -"@helium/voter-stake-registry-sdk@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.16.tgz#859f9f726513f286c32ec9416ec98956d56cd9ba" - integrity sha512-QiPVNoixj5XrHZMF0UmyBP83o/T9YZE7EE1RxL9AgjBjcKZjan2OfFb91BwwS377HP9uSFZkrV0ZilAscTep6A== +"@helium/voter-stake-registry-sdk@^0.2.17": + version "0.2.17" + resolved "https://registry.yarnpkg.com/@helium/voter-stake-registry-sdk/-/voter-stake-registry-sdk-0.2.17.tgz#3411a60c25d687502ed2dc6fe8f5b0f4aa3c4046" + integrity sha512-WyDHS/JE6RzA4mKC1EXBG4HZv9BqiMZjGnRkNTwlhEAvfaxB44XUlhZpTEIIffiFbzACxHYFaBm10HokwgVFPw== dependencies: "@coral-xyz/anchor" "^0.26.0" - "@helium/anchor-resolvers" "^0.2.16" - "@helium/idls" "^0.2.16" + "@helium/anchor-resolvers" "^0.2.17" + "@helium/idls" "^0.2.17" "@metaplex-foundation/mpl-token-metadata" "^2.2.3" "@solana/spl-token" "^0.3.6" bn.js "^5.2.0" From aa1bcc459ca16714744d99ea7d888fce8fef1aca Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Sat, 5 Aug 2023 10:06:30 -0500 Subject: [PATCH 15/23] Add back dapp login --- .../org.eclipse.buildship.core.prefs | 4 +- src/App.tsx | 62 ++-- src/features/dappLogin/DappAccount.tsx | 139 ++++++++ src/features/dappLogin/DappConnect.tsx | 80 +++++ src/features/dappLogin/DappLoginScreen.tsx | 243 ++++++++++++++ .../dappLogin/WalletConnectProvider.tsx | 300 ++++++++++++++++++ src/locales/en.ts | 23 ++ src/navigation/RootNavigator.tsx | 40 ++- src/navigation/rootTypes.ts | 1 + 9 files changed, 844 insertions(+), 48 deletions(-) create mode 100644 src/features/dappLogin/DappAccount.tsx create mode 100644 src/features/dappLogin/DappConnect.tsx create mode 100644 src/features/dappLogin/DappLoginScreen.tsx create mode 100644 src/features/dappLogin/WalletConnectProvider.tsx diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index 98515123a..b278339ce 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,11 +1,11 @@ -arguments= +arguments=--init-script /var/folders/ck/r25tcygs77n6hv8d515p1w_c0000gn/T/d146c9752a26f79b52047fb6dc6ed385d064e120494f96f08ca63a317c41f94c.gradle --init-script /var/folders/ck/r25tcygs77n6hv8d515p1w_c0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home +java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/src/App.tsx b/src/App.tsx index aca2c8d8f..48a9fc476 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import OneSignal, { OpenedEvent } from 'react-native-onesignal' import { SafeAreaProvider } from 'react-native-safe-area-context' import NetworkAwareStatusBar from './components/NetworkAwareStatusBar' import SplashScreen from './components/SplashScreen' +import WalletConnectProvider from './features/dappLogin/WalletConnectProvider' import LockScreen from './features/lock/LockScreen' import OnboardingProvider from './features/onboarding/OnboardingProvider' import SecurityScreen from './features/security/SecurityScreen' @@ -112,35 +113,38 @@ const App = () => { - {cache && ( - - - {accountsRestored && ( - <> - - - - - - - - - - - - - )} - - - )} + + {cache && ( + + + {accountsRestored && ( + <> + + + + + + + + + + + + + )} + + + )} + diff --git a/src/features/dappLogin/DappAccount.tsx b/src/features/dappLogin/DappAccount.tsx new file mode 100644 index 000000000..1df7e60be --- /dev/null +++ b/src/features/dappLogin/DappAccount.tsx @@ -0,0 +1,139 @@ +import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import Crowdspot from '@assets/images/crowdspot.svg' +import AddDapp from '@assets/images/addDapp.svg' +import DappEllipsis from '@assets/images/dapp-ellipsis.svg' +import { NetTypes as NetType, NetTypes } from '@helium/address' +import { ActivityIndicator } from 'react-native' +import AccountButton from '@components/AccountButton' +import AccountSelector, { + AccountSelectorRef, +} from '@components/AccountSelector' +import Box from '@components/Box' +import Text from '@components/Text' +import TouchableOpacityBox from '@components/TouchableOpacityBox' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import AccountIcon from '@components/AccountIcon' +import { useColors } from '@theme/themeHooks' + +type Props = { + onLogin: () => void + appName: string + onCancel: () => void + loading: boolean +} +const DappLogin = ({ onLogin, onCancel, appName, loading }: Props) => { + const { + currentAccount, + setCurrentAccount, + sortedMainnetAccounts, + currentNetworkAddress, + } = useAccountStorage() + const { t } = useTranslation() + const accountSelectorRef = useRef(null) + const colors = useColors() + + const isCrowdspot = useMemo( + () => appName.toLowerCase() === 'crowdspot', + [appName], + ) + + const handleAccountButtonPress = useCallback(() => { + if (!accountSelectorRef?.current) return + + accountSelectorRef.current.showAccountTypes(NetTypes.MAINNET)() + }, []) + + useEffect(() => { + if (currentAccount?.netType !== NetType.MAINNET) { + setCurrentAccount( + sortedMainnetAccounts.length ? sortedMainnetAccounts[0] : null, + ) + } + }, [currentAccount, setCurrentAccount, sortedMainnetAccounts]) + + return ( + + + + + {isCrowdspot ? ( + + ) : ( + + )} + + + + + + + {t('dappLogin.account.title', { + appName, + })} + + + {t('dappLogin.account.subtitle', { appName })} + + + + + + + {t('generic.cancel')} + + + + {loading ? ( + + ) : ( + + {t('dappLogin.login')} + + )} + + + + + ) +} + +export default memo(DappLogin) diff --git a/src/features/dappLogin/DappConnect.tsx b/src/features/dappLogin/DappConnect.tsx new file mode 100644 index 000000000..e7be44d1f --- /dev/null +++ b/src/features/dappLogin/DappConnect.tsx @@ -0,0 +1,80 @@ +import React, { memo, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import Crowdspot from '@assets/images/crowdspot.svg' +import AddDapp from '@assets/images/addDapp.svg' +import Box from '@components/Box' +import Text from '@components/Text' +import TouchableOpacityBox from '@components/TouchableOpacityBox' +import { useColors } from '@theme/themeHooks' + +type Props = { appName: string; onApprove: () => void; onDeny: () => void } +const DappConnect = ({ appName, onApprove, onDeny }: Props) => { + const { t } = useTranslation() + const { primaryText } = useColors() + + const isCrowdspot = useMemo( + () => appName.toLowerCase() === 'crowdspot', + [appName], + ) + + return ( + + + {isCrowdspot ? ( + + ) : ( + + )} + + {t('dappLogin.connect.title', { appName })} + + + {t('dappLogin.connect.subtitle', { appName })} + + + + + + + {t('generic.cancel')} + + + + + {t('dappLogin.connect.continue')} + + + + + ) +} + +export default memo(DappConnect) diff --git a/src/features/dappLogin/DappLoginScreen.tsx b/src/features/dappLogin/DappLoginScreen.tsx new file mode 100644 index 000000000..f404bd749 --- /dev/null +++ b/src/features/dappLogin/DappLoginScreen.tsx @@ -0,0 +1,243 @@ +import Close from '@assets/images/close.svg' +import SafeAreaBox from '@components/SafeAreaBox' +import TouchableOpacityBox from '@components/TouchableOpacityBox' +import Address from '@helium/address' +import { TokenBurnV1 } from '@helium/transactions' +import useAlert from '@hooks/useAlert' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' +import { useColors } from '@theme/themeHooks' +import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react' +import { useAsync } from 'react-async-hook' +import { useTranslation } from 'react-i18next' +import { ActivityIndicator, Linking } from 'react-native' +import { useDebouncedCallback } from 'use-debounce/lib' +import { + RootNavigationProp, + RootStackParamList, +} from '../../navigation/rootTypes' +import { useAccountStorage } from '../../storage/AccountStorageProvider' +import { getKeypair } from '../../storage/secureStorage' +import { HomeNavigationProp } from '../home/homeTypes' +import DappAccount from './DappAccount' +import DappConnect from './DappConnect' +import { useWalletConnect } from './WalletConnectProvider' + +export const EMPTY_B58_ADDRESS = Address.fromB58( + '13PuqyWXzPYeXcF1B9ZRx7RLkEygeL374ZABiQdwRSNzASdA1sn', +) +const makeBurnTxn = async (opts: { payerB58: string }) => { + const { payerB58 } = opts + + const txn = new TokenBurnV1({ + amount: 1, + payer: Address.fromB58(payerB58), + payee: EMPTY_B58_ADDRESS, + nonce: 0, + memo: '', + }) + + const txnJson = { + type: txn.type, + payee: txn.payee?.b58 || '', + amount: 1, + payer: txn.payer?.b58, + nonce: txn.nonce, + fee: txn.fee, + memo: txn.memo, + } + + const keypair = await getKeypair(payerB58) + + if (!keypair) throw new Error('Keypair not found') + const signedTxn = await txn.sign({ payer: keypair }) + return { signedTxn, txnJson: JSON.stringify(txnJson), unsignedTxn: txn } +} + +type Route = RouteProp +const DappLoginScreen = () => { + const route = useRoute() + const navigation = useNavigation() + const rootNav = useNavigation() + const { params } = route + const { + allowLogin, + approvePair, + connectionState, + denyPair, + disconnect, + login, + loginRequest, + sessionProposal, + pairClient, + } = useWalletConnect() + const { t } = useTranslation() + const colors = useColors() + const { currentAccount } = useAccountStorage() + const { primaryText } = useColors() + const { showOKAlert } = useAlert() + const hasRequestedPair = useRef(false) + + useAsync(async () => { + if (params.uri.includes('wc:') && !hasRequestedPair.current) { + hasRequestedPair.current = true + try { + await pairClient(params.uri) + } catch (error) { + await showOKAlert({ + title: t('dappLogin.error', { + appName: sessionProposal?.params.proposer.metadata.name, + }), + message: (error as Error).toString(), + }) + } + } + }, [pairClient, params.callback, params.uri]) + + const goBack = useCallback(async () => { + await disconnect() + + if (navigation.canGoBack()) { + navigation.goBack() + } else { + rootNav.reset({ + index: 0, + routes: [{ name: 'TabBarNavigator' }], + }) + } + }, [disconnect, navigation, rootNav]) + + const handleDeny = useCallback(async () => { + await denyPair() + await goBack() + }, [denyPair, goBack]) + + const handleAllowLogin = useDebouncedCallback( + () => { + allowLogin() + }, + 1000, + { + leading: true, + trailing: false, + }, + ) + + const handleLogin = useCallback(async () => { + if (!currentAccount?.address) return + + try { + await approvePair(currentAccount.address) + } catch (error) { + await showOKAlert({ + title: t('dappLogin.error', { + appName: sessionProposal?.params.proposer.metadata.name, + }), + message: (error as Error).toString(), + }) + } + }, [approvePair, currentAccount, sessionProposal, showOKAlert, t]) + + useAsync(async () => { + if (!currentAccount?.address || !loginRequest) return + + const { signedTxn } = await makeBurnTxn({ + payerB58: currentAccount.address, + }) + + if (!signedTxn) return + + try { + await login({ + txn: signedTxn.toString(), + address: currentAccount.address, + }) + } catch (error) { + await showOKAlert({ + title: t('dappLogin.error', { + appName: sessionProposal?.params.proposer.metadata.name, + }), + message: (error as Error).toString(), + }) + } + + await goBack() + + if (params.callback && (await Linking.canOpenURL(params.callback))) { + await Linking.openURL(params.callback) + } + }, [currentAccount, loginRequest]) + + const checkTimeoutError = useCallback(async () => { + if (connectionState !== 'undetermined') return + await showOKAlert({ + title: t('dappLogin.timeoutAlert.title'), + message: t('dappLogin.timeoutAlert.message'), + }) + await goBack() + }, [connectionState, goBack, showOKAlert, t]) + + // if connectionState doesn't update after 5 seconds show timeout error + useEffect(() => { + const timer = setTimeout(checkTimeoutError, 5000) + return () => { + clearTimeout(timer) + } + }, [connectionState, goBack, checkTimeoutError, showOKAlert, t]) + + const body = useMemo(() => { + if (!sessionProposal || connectionState === 'undetermined') { + return ( + + + + ) + } + if (connectionState === 'proposal') { + return ( + + ) + } + + return ( + + ) + }, [ + connectionState, + handleAllowLogin, + handleDeny, + handleLogin, + sessionProposal, + goBack, + primaryText, + ]) + + return ( + + + + + {body} + + ) +} + +export default memo(DappLoginScreen) diff --git a/src/features/dappLogin/WalletConnectProvider.tsx b/src/features/dappLogin/WalletConnectProvider.tsx new file mode 100644 index 000000000..5040c159d --- /dev/null +++ b/src/features/dappLogin/WalletConnectProvider.tsx @@ -0,0 +1,300 @@ +import React, { + createContext, + ReactNode, + useCallback, + useContext, + useState, +} from 'react' +import '@walletconnect/react-native-compat' +import SignClient from '@walletconnect/sign-client' +import { + PairingTypes, + SessionTypes, + SignClientTypes, +} from '@walletconnect/types' +import Config from 'react-native-config' +import * as Logger from '../../utils/logger' + +const breadcrumbOpts = { category: 'Wallet Connect' } + +const NAMESPACE = 'helium' +const MAINNET = `${NAMESPACE}:mainnet` +type ConnectionState = + | 'undetermined' + | 'proposal' + | 'allowed' + | 'denied' + | 'approving' + | 'approved' +const useWalletConnectHook = () => { + const [signClient, setSignClient] = useState() + const [sessionProposal, setSessionProposal] = + useState() + const [connectionState, setConnectionState] = + useState('undetermined') + const [loginRequest, setLoginRequest] = + useState() + const [approvalRequest, setApprovalRequest] = useState<{ topic: string }>() + + const pairClient = useCallback( + async (uri: string): Promise => { + Logger.breadcrumb('pair client requested', breadcrumbOpts) + + try { + let client = signClient + if (!client) { + Logger.breadcrumb('Begin Initialize Client', breadcrumbOpts) + + client = await SignClient.init({ + projectId: Config.WALLET_CONNECT_PROJECT_ID, + relayUrl: 'wss://relay.walletconnect.com', + metadata: { + name: 'Helium Wallet', + description: 'Helium Wallet', + url: Config.WALLET_CONNECT_METADATA_URL, + icons: [], + }, + }) + + Logger.breadcrumb('Client initialized', breadcrumbOpts) + + setSignClient(client) + + client.on('session_proposal', async (proposal) => { + Logger.breadcrumb('Session proposal created', breadcrumbOpts) + setSessionProposal(proposal) + setConnectionState('proposal') + }) + + client.on('session_request', async (loginReqEvent) => { + if (loginReqEvent.params.request.method !== 'personal_sign') return + + Logger.breadcrumb('Login request event created', breadcrumbOpts) + setLoginRequest(loginReqEvent) + }) + } else { + Logger.breadcrumb('Client already initialized', breadcrumbOpts) + } + + Logger.breadcrumb('client.pair - begin', breadcrumbOpts) + const pairResponse = await client.pair({ uri }) + Logger.breadcrumb('client.pair - success', breadcrumbOpts) + return pairResponse + } catch (err) { + Logger.breadcrumb('pairClient - fail', breadcrumbOpts) + Logger.error(err) + throw err + } + }, + [setLoginRequest, signClient], + ) + + const approvePair = useCallback( + async ( + address: string, + ): Promise< + | { + topic: string + acknowledged: () => Promise + } + | undefined + > => { + Logger.breadcrumb('approvePair', breadcrumbOpts) + + setConnectionState('approving') + + try { + if (!sessionProposal || !signClient) { + Logger.breadcrumb( + `Approve pair requested, but client not ready. ${JSON.stringify({ + loginProposal: !!sessionProposal, + walletClient: !!signClient, + })}`, + breadcrumbOpts, + ) + return await new Promise(() => {}) + } + + const nextApprovalResponse = await signClient.approve({ + id: sessionProposal.id, + namespaces: { + [NAMESPACE]: { + accounts: [`${MAINNET}:${address}`], + methods: ['personal_sign'], + events: [], + }, + }, + }) + + setConnectionState('approved') + + Logger.breadcrumb('approvePair - success', breadcrumbOpts) + setApprovalRequest(nextApprovalResponse) + return nextApprovalResponse + } catch (err) { + Logger.breadcrumb('approvePair - fail', breadcrumbOpts) + Logger.error(err) + throw err + } + }, + [sessionProposal, signClient], + ) + + const allowLogin = useCallback(() => { + Logger.breadcrumb('allowLogin', breadcrumbOpts) + + setConnectionState('allowed') + }, []) + + const denyPair = useCallback(async () => { + Logger.breadcrumb('denyPair', breadcrumbOpts) + + if (!sessionProposal || !signClient) { + Logger.breadcrumb( + `Deny pair requested, but client not ready. ${JSON.stringify({ + loginProposal: !!sessionProposal, + walletClient: !!signClient, + })}`, + breadcrumbOpts, + ) + return + } + + Logger.breadcrumb('denyPair - begin client reject', breadcrumbOpts) + const nextDenyResponse = await signClient.reject({ + id: sessionProposal.id, + reason: { code: 1000, message: 'denied by user' }, + }) + + Logger.breadcrumb('denyPair - client reject success', breadcrumbOpts) + + setConnectionState('denied') + + return nextDenyResponse + }, [sessionProposal, signClient]) + + const disconnect = useCallback(async () => { + Logger.breadcrumb('disconnect', breadcrumbOpts) + + try { + if (connectionState === 'undetermined') { + await denyPair() + } + + if (signClient) { + if (loginRequest?.topic || approvalRequest?.topic) { + Logger.breadcrumb('disconnect wallet client - begin', breadcrumbOpts) + await signClient.disconnect({ + topic: loginRequest?.topic || approvalRequest?.topic || '', + reason: { + code: 1, + message: 'finished', + }, + }) + Logger.breadcrumb( + 'disconnect wallet client - success', + breadcrumbOpts, + ) + } else if ( + sessionProposal?.id && + connectionState !== 'undetermined' && + connectionState !== 'denied' + ) { + signClient.reject({ + id: sessionProposal.id, + reason: { code: 2000, message: 'Login Process Closed' }, + }) + } + } + setSignClient(undefined) + setSessionProposal(undefined) + setLoginRequest(undefined) + setApprovalRequest(undefined) + setConnectionState('undetermined') + } catch (err) { + Logger.breadcrumb('disconnect - fail', breadcrumbOpts) + Logger.error(err) + throw err + } + }, [ + approvalRequest, + connectionState, + denyPair, + loginRequest, + sessionProposal, + signClient, + ]) + + const login = useCallback( + async (opts: { txn: string; address: string }) => { + Logger.breadcrumb('login', breadcrumbOpts) + + try { + if (!loginRequest || !signClient) { + Logger.breadcrumb( + `Login requested, but client not ready. ${JSON.stringify({ + loginProposal: !!sessionProposal, + walletClient: !!signClient, + })}`, + breadcrumbOpts, + ) + return + } + + const { topic } = loginRequest + + const responseBody = { + topic, + response: { + id: loginRequest.id, + jsonrpc: '2.0', + result: opts, + }, + } + await signClient.respond(responseBody) + Logger.breadcrumb('login - success', breadcrumbOpts) + } catch (err) { + Logger.breadcrumb('login - fail', breadcrumbOpts) + Logger.error(err) + throw err + } + }, + [sessionProposal, loginRequest, signClient], + ) + + return { + allowLogin, + approvePair, + connectionState, + denyPair, + disconnect, + login, + sessionProposal, + loginRequest, + pairClient, + } +} + +const initialState = { + approvePair: () => new Promise((resolve) => resolve(undefined)), + allowLogin: () => undefined, + connectionState: 'undetermined' as ConnectionState, + denyPair: () => new Promise((resolve) => resolve()), + disconnect: () => new Promise((resolve) => resolve()), + login: () => new Promise((resolve) => resolve()), + sessionProposal: undefined, + loginRequest: undefined, + pairClient: () => new Promise((resolve) => resolve()), +} + +const WalletConnectContext = + createContext>(initialState) +const { Provider } = WalletConnectContext + +const WalletConnectProvider = ({ children }: { children: ReactNode }) => { + return {children} +} + +export const useWalletConnect = () => useContext(WalletConnectContext) + +export default WalletConnectProvider diff --git a/src/locales/en.ts b/src/locales/en.ts index 30af9b86e..4aaaa8184 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -480,6 +480,29 @@ export default { add: 'Add New Wallet', addTestnet: 'Add New Testnet Wallet', }, + dappLogin: { + account: { + subtitle: 'Which wallet do you want to authenticate with {{appName}}?', + title: 'Choose your\nWallet', + }, + connect: { + continue: 'Continue', + subtitle: 'Authenticate {{appName}}\nwith your Helium Wallet?', + title: 'Connect to {{appName}}?', + }, + error: 'Failed to verify {{appName}}', + ledger: { + subtitle: + 'You must sign burn transaction to login to {{appName}}. Please verify the burn transaction on your Ledger device {{deviceName}}', + title: 'Ledger Approval', + }, + login: 'Login', + timeoutAlert: { + title: 'Login Failed', + message: + 'Please close and reopen the login screen in Crowdspot and scan a new QR code to try again.', + }, + }, editContact: { delete: 'Delete', deleteConfirmMessage: diff --git a/src/navigation/RootNavigator.tsx b/src/navigation/RootNavigator.tsx index 285d126e9..b47a8b9fb 100644 --- a/src/navigation/RootNavigator.tsx +++ b/src/navigation/RootNavigator.tsx @@ -1,30 +1,31 @@ -import React, { memo, useCallback, useEffect, useRef } from 'react' -import changeNavigationBarColor from 'react-native-navigation-bar-color' +import { useNavigation } from '@react-navigation/native' import { - createStackNavigator, StackNavigationOptions, + createStackNavigator, } from '@react-navigation/stack' -import { useNavigation } from '@react-navigation/native' -import { useSelector } from 'react-redux' import { useColors } from '@theme/themeHooks' -import { - RootStackParamList, - RootNavigationProp, - TabBarNavigationProp, -} from './rootTypes' -import OnboardingNavigator from '../features/onboarding/OnboardingNavigator' -import TabBarNavigator from './TabBarNavigator' -import { HomeNavigationProp } from '../features/home/homeTypes' +import React, { memo, useCallback, useEffect, useRef } from 'react' +import changeNavigationBarColor from 'react-native-navigation-bar-color' +import { useSelector } from 'react-redux' +import DappLoginScreen from 'src/features/dappLogin/DappLoginScreen' import ConnectedWallets, { ConnectedWalletsRef, } from '../features/account/ConnectedWallets' +import { HomeNavigationProp } from '../features/home/homeTypes' +import OnboardingNavigator from '../features/onboarding/OnboardingNavigator' +import ImportPrivateKey from '../features/onboarding/import/ImportPrivateKey' +import PaymentScreen from '../features/payment/PaymentScreen' +import LinkWallet from '../features/txnDelegation/LinkWallet' +import SignHotspot from '../features/txnDelegation/SignHotspot' import { RootState } from '../store/rootReducer' import { appSlice } from '../store/slices/appSlice' import { useAppDispatch } from '../store/store' -import LinkWallet from '../features/txnDelegation/LinkWallet' -import PaymentScreen from '../features/payment/PaymentScreen' -import SignHotspot from '../features/txnDelegation/SignHotspot' -import ImportPrivateKey from '../features/onboarding/import/ImportPrivateKey' +import TabBarNavigator from './TabBarNavigator' +import { + RootNavigationProp, + RootStackParamList, + TabBarNavigationProp, +} from './rootTypes' const screenOptions = { headerShown: false } as StackNavigationOptions @@ -93,6 +94,11 @@ const RootNavigator = () => { component={PaymentScreen} options={screenOptions} /> + Date: Mon, 7 Aug 2023 09:03:44 -0500 Subject: [PATCH 16/23] Fix build --- src/features/account/AirdropScreen.tsx | 2 +- src/features/account/useTxn.tsx | 3 +- .../collectables/ClaimRewardsScreen.tsx | 25 ++++++------- src/features/collectables/HotspotListItem.tsx | 4 +-- src/features/ledger/LedgerAccountListItem.tsx | 1 + src/hooks/useIotInfo.ts | 9 ++--- src/hooks/useMobileInfo.ts | 9 ++--- src/hooks/useRecipient.tsx | 35 ------------------- 8 files changed, 29 insertions(+), 59 deletions(-) delete mode 100644 src/hooks/useRecipient.tsx diff --git a/src/features/account/AirdropScreen.tsx b/src/features/account/AirdropScreen.tsx index 9f36f881a..5db0db711 100644 --- a/src/features/account/AirdropScreen.tsx +++ b/src/features/account/AirdropScreen.tsx @@ -168,7 +168,7 @@ const AirdropScreen = () => { marginBottom="l" > - + diff --git a/src/features/account/useTxn.tsx b/src/features/account/useTxn.tsx index b5c131fb5..4785f9ee5 100644 --- a/src/features/account/useTxn.tsx +++ b/src/features/account/useTxn.tsx @@ -10,6 +10,7 @@ import { useMetaplexMetadata, } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' +import { Mint } from '@solana/spl-token' import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' import { Color } from '@theme/theme' import { useColors } from '@theme/themeHooks' @@ -63,7 +64,7 @@ const useTxn = ( const decimalsByMint = useMemo(() => { return mintAccs?.reduce((acc, curr) => { if (curr.info) { - acc[curr.publicKey.toBase58()] = curr.info.decimals + acc[curr.publicKey.toBase58()] = (curr.info as Mint).decimals } return acc }, {} as { [key: string]: number }) diff --git a/src/features/collectables/ClaimRewardsScreen.tsx b/src/features/collectables/ClaimRewardsScreen.tsx index 5ccd9beb9..a96970061 100644 --- a/src/features/collectables/ClaimRewardsScreen.tsx +++ b/src/features/collectables/ClaimRewardsScreen.tsx @@ -1,20 +1,21 @@ -import React, { useMemo, memo, useState } from 'react' -import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' -import { useTranslation } from 'react-i18next' -import { Edge } from 'react-native-safe-area-context' -import { PublicKey, Transaction } from '@solana/web3.js' -import BN from 'bn.js' -import CircleLoader from '@components/CircleLoader' import { ReAnimatedBox } from '@components/AnimatedBox' import BackScreen from '@components/BackScreen' import Box from '@components/Box' -import Text from '@components/Text' import ButtonPressable from '@components/ButtonPressable' +import CircleLoader from '@components/CircleLoader' import { DelayedFadeIn } from '@components/FadeInOut' -import { useHotspot } from '@hooks/useHotspot' import RewardItem from '@components/RewardItem' -import { Mints } from '../../utils/constants' +import Text from '@components/Text' +import { IOT_MINT, MOBILE_MINT } from '@helium/spl-utils' +import { useHotspot } from '@hooks/useHotspot' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' +import { PublicKey, Transaction } from '@solana/web3.js' +import BN from 'bn.js' +import React, { memo, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Edge } from 'react-native-safe-area-context' import useSubmitTxn from '../../hooks/useSubmitTxn' +import { Mints } from '../../utils/constants' import { CollectableNavigationProp, CollectableStackParamList, @@ -134,14 +135,14 @@ const ClaimRewardsScreen = () => { > {!!pendingMobileRewards && pendingMobileRewards.gt(new BN(0)) && ( )} {!!pendingIotRewards && pendingIotRewards.gt(new BN(0)) && ( diff --git a/src/features/collectables/HotspotListItem.tsx b/src/features/collectables/HotspotListItem.tsx index 354dd1f68..959dc21a3 100644 --- a/src/features/collectables/HotspotListItem.tsx +++ b/src/features/collectables/HotspotListItem.tsx @@ -45,7 +45,7 @@ const HotspotListItem = ({ if (!hotspot.pendingRewards) return const num = toNumber( new BN(hotspot.pendingRewards[Mints.IOT]), - iotMint?.info.decimals || 6, + iotMint?.decimals || 6, ) return formatLargeNumber(new BigNumber(num)) }, [iotMint, hotspot]) @@ -60,7 +60,7 @@ const HotspotListItem = ({ if (!hotspot.pendingRewards) return const num = toNumber( new BN(hotspot.pendingRewards[Mints.MOBILE]), - mobileMint?.info.decimals || 6, + mobileMint?.decimals || 6, ) return formatLargeNumber(new BigNumber(num)) }, [hotspot, mobileMint]) diff --git a/src/features/ledger/LedgerAccountListItem.tsx b/src/features/ledger/LedgerAccountListItem.tsx index 4ff883e84..fc96213fc 100644 --- a/src/features/ledger/LedgerAccountListItem.tsx +++ b/src/features/ledger/LedgerAccountListItem.tsx @@ -2,6 +2,7 @@ import AccountIcon from '@components/AccountIcon' import Box from '@components/Box' import Surface from '@components/Surface' import Text from '@components/Text' +import { toBN } from '@helium/spl-utils' import { LedgerAccount } from '@hooks/useLedger' import CheckBox from '@react-native-community/checkbox' import { useColors } from '@theme/themeHooks' diff --git a/src/hooks/useIotInfo.ts b/src/hooks/useIotInfo.ts index 16ca0dd58..f067aa22a 100644 --- a/src/hooks/useIotInfo.ts +++ b/src/hooks/useIotInfo.ts @@ -1,12 +1,13 @@ -import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' -import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' import { IdlAccounts } from '@coral-xyz/anchor' -import { PublicKey } from '@solana/web3.js' -import { UseAccountState, useIdlAccount } from '@helium/helium-react-hooks' +import { UseAccountState } from '@helium/account-fetch-cache-hooks' import { iotInfoKey, rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' +import { useIdlAccount } from '@helium/helium-react-hooks' +import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' +import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' +import { PublicKey } from '@solana/web3.js' import { IOT_SUB_DAO_KEY } from '@utils/constants' const type = 'iotHotspotInfoV0' diff --git a/src/hooks/useMobileInfo.ts b/src/hooks/useMobileInfo.ts index c5eb5a031..4cdd65706 100644 --- a/src/hooks/useMobileInfo.ts +++ b/src/hooks/useMobileInfo.ts @@ -1,12 +1,13 @@ -import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' -import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' import { IdlAccounts } from '@coral-xyz/anchor' -import { PublicKey } from '@solana/web3.js' -import { UseAccountState, useIdlAccount } from '@helium/helium-react-hooks' +import { UseAccountState } from '@helium/account-fetch-cache-hooks' import { mobileInfoKey, rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' +import { useIdlAccount } from '@helium/helium-react-hooks' +import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' +import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' +import { PublicKey } from '@solana/web3.js' import { MOBILE_SUB_DAO_KEY } from '@utils/constants' const type = 'mobileHotspotInfoV0' diff --git a/src/hooks/useRecipient.tsx b/src/hooks/useRecipient.tsx deleted file mode 100644 index 790e7d06e..000000000 --- a/src/hooks/useRecipient.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { IDL } from '@helium/idls/lib/esm/lazy_distributor' -import { LazyDistributor } from '@helium/idls/lib/types/lazy_distributor' -import { PublicKey } from '@solana/web3.js' -import { IdlAccounts } from '@coral-xyz/anchor' -import { UseAccountState, useIdlAccount } from '@helium/helium-react-hooks' -import { useAsync } from 'react-async-hook' -import { useCallback, useState } from 'react' - -export type Recipient = IdlAccounts['recipientV0'] & { - pubkey: PublicKey -} -const t = 'recipientV0' -export function useRecipient(key: PublicKey): UseAccountState { - const [, updateState] = useState() - - const forceUpdate = useCallback(() => updateState({}), []) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - const { loading, info } = useIdlAccount( - key, - IDL as LazyDistributor, - t, - ) - - useAsync(async () => { - if (!info && !loading) { - forceUpdate() - } - }, [info, loading]) - - return { - loading, - info: info as Recipient, - } -} From 60ea8b8bf6262a120fdb04c41219d26fc46c84e0 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Mon, 7 Aug 2023 09:39:09 -0500 Subject: [PATCH 17/23] Fix build --- src/features/account/useTxn.tsx | 11 ++++++++--- src/features/ledger/LedgerAccountListItem.tsx | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/features/account/useTxn.tsx b/src/features/account/useTxn.tsx index 4785f9ee5..57e271949 100644 --- a/src/features/account/useTxn.tsx +++ b/src/features/account/useTxn.tsx @@ -1,7 +1,6 @@ import TxnReceive from '@assets/images/txnReceive.svg' import TxnSend from '@assets/images/txnSend.svg' import { useAccounts } from '@helium/account-fetch-cache-hooks' -import { MintParser } from '@helium/helium-react-hooks' import { truthy } from '@helium/spl-utils' import { useCurrentWallet } from '@hooks/useCurrentWallet' import { @@ -10,8 +9,8 @@ import { useMetaplexMetadata, } from '@hooks/useMetaplexMetadata' import { usePublicKey } from '@hooks/usePublicKey' -import { Mint } from '@solana/spl-token' -import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' +import { Mint, unpackMint } from '@solana/spl-token' +import { AccountInfo, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js' import { Color } from '@theme/theme' import { useColors } from '@theme/themeHooks' import BN from 'bn.js' @@ -30,6 +29,12 @@ import { Activity } from '../../types/activity' import shortLocale from '../../utils/formatDistance' import { TXN_FEE_IN_LAMPORTS, humanReadable } from '../../utils/solanaUtils' +export const MintParser = (pubKey: PublicKey, info: AccountInfo) => { + const data = unpackMint(pubKey, info) + + return data +} + export const TxnTypeKeys = ['payment_v2'] as const type TxnType = typeof TxnTypeKeys[number] diff --git a/src/features/ledger/LedgerAccountListItem.tsx b/src/features/ledger/LedgerAccountListItem.tsx index fc96213fc..b41e40e63 100644 --- a/src/features/ledger/LedgerAccountListItem.tsx +++ b/src/features/ledger/LedgerAccountListItem.tsx @@ -36,7 +36,7 @@ const LedgerAccountListItem = ({ const colors = useColors() // TODO: Add other token types once nano app supports them - const balance = toBN(account.balance, 8) + const balance = toBN(account.balance || 0, 8) const disabled = section.index === Section.ALREADY_LINKED const borderTopEndRadius = useMemo( From 9e21947dd8c1a48fb896f85ac34bc7a89feb0e70 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Mon, 7 Aug 2023 10:56:27 -0500 Subject: [PATCH 18/23] Fix build --- package.json | 2 +- src/features/dappLogin/DappLoginScreen.tsx | 5 +---- src/navigation/RootNavigator.tsx | 2 +- yarn.lock | 11 +---------- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 3729c6e4d..18e85d485 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@gorhom/portal": "1.0.14", "@helium/account-fetch-cache": "^0.2.17", "@helium/account-fetch-cache-hooks": "^0.2.17", - "@helium/address": "4.6.2", + "@helium/address": "4.10.2", "@helium/circuit-breaker-sdk": "^0.2.17", "@helium/crypto-react-native": "4.8.0", "@helium/currency-utils": "0.1.1", diff --git a/src/features/dappLogin/DappLoginScreen.tsx b/src/features/dappLogin/DappLoginScreen.tsx index f404bd749..35548f2f5 100644 --- a/src/features/dappLogin/DappLoginScreen.tsx +++ b/src/features/dappLogin/DappLoginScreen.tsx @@ -22,16 +22,13 @@ import DappAccount from './DappAccount' import DappConnect from './DappConnect' import { useWalletConnect } from './WalletConnectProvider' -export const EMPTY_B58_ADDRESS = Address.fromB58( - '13PuqyWXzPYeXcF1B9ZRx7RLkEygeL374ZABiQdwRSNzASdA1sn', -) const makeBurnTxn = async (opts: { payerB58: string }) => { const { payerB58 } = opts const txn = new TokenBurnV1({ amount: 1, payer: Address.fromB58(payerB58), - payee: EMPTY_B58_ADDRESS, + payee: Address.fromB58(payerB58), nonce: 0, memo: '', }) diff --git a/src/navigation/RootNavigator.tsx b/src/navigation/RootNavigator.tsx index b47a8b9fb..38eeb4c94 100644 --- a/src/navigation/RootNavigator.tsx +++ b/src/navigation/RootNavigator.tsx @@ -7,7 +7,7 @@ import { useColors } from '@theme/themeHooks' import React, { memo, useCallback, useEffect, useRef } from 'react' import changeNavigationBarColor from 'react-native-navigation-bar-color' import { useSelector } from 'react-redux' -import DappLoginScreen from 'src/features/dappLogin/DappLoginScreen' +import DappLoginScreen from '../features/dappLogin/DappLoginScreen' import ConnectedWallets, { ConnectedWalletsRef, } from '../features/account/ConnectedWallets' diff --git a/yarn.lock b/yarn.lock index 94278db60..2f750a3af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2275,16 +2275,7 @@ dependencies: "@solana/web3.js" "^1.43.4" -"@helium/address@4.6.2": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.6.2.tgz#0356bd9693ff7184f93f3c4e12c04f4f9c1db7b6" - integrity sha512-MXlT9SH4HPrBj+r71w51grZj4MUNi4o4SnDJds/yallROBK0qlO8m/bDCsKfmj0FVm861tIfQBrJc6y5vjsymg== - dependencies: - bs58 "^5.0.0" - js-sha256 "^0.9.0" - multiformats "^9.6.4" - -"@helium/address@^4.10.2": +"@helium/address@4.10.2", "@helium/address@^4.10.2": version "4.10.2" resolved "https://registry.yarnpkg.com/@helium/address/-/address-4.10.2.tgz#56960b118fceb6b6ddabe3e4ecec467d9ae50e26" integrity sha512-qCswC7Z3GXuJyHv36RcOSnffeghjqJQx0fdu2Lxpf9fgOnIi1JZO2tjjk1mBaqOwCyp+0YzrTPUoEukL/WCtsA== From fed130df3dc6d15bff76844cae0a0ddb6429ab97 Mon Sep 17 00:00:00 2001 From: Noah Prince <83885631+ChewingGlass@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:01:31 -0500 Subject: [PATCH 19/23] Feat/12 word warning (#414) * Fix dapp login * Add more warnings on key export --- src/features/dappLogin/DappLoginScreen.tsx | 6 +++++- src/locales/en.ts | 4 ++-- src/utils/linking.ts | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/features/dappLogin/DappLoginScreen.tsx b/src/features/dappLogin/DappLoginScreen.tsx index 35548f2f5..1c6a273a4 100644 --- a/src/features/dappLogin/DappLoginScreen.tsx +++ b/src/features/dappLogin/DappLoginScreen.tsx @@ -28,7 +28,11 @@ const makeBurnTxn = async (opts: { payerB58: string }) => { const txn = new TokenBurnV1({ amount: 1, payer: Address.fromB58(payerB58), - payee: Address.fromB58(payerB58), + // TODO: This must not be a global const or checksum fails for some reason?? + // This whole login process should go away anyway. + payee: Address.fromB58( + '13PuqyWXzPYeXcF1B9ZRx7RLkEygeL374ZABiQdwRSNzASdA1sn', + ), nonce: 0, memo: '', }) diff --git a/src/locales/en.ts b/src/locales/en.ts index 4aaaa8184..036cee8d2 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -804,14 +804,14 @@ export default { alertTitle: 'Are you sure?', done: 'Done', subtitle: - 'Do not share your private key!\n\nIf someone has your private key they will have full control of your wallet!', + 'Do not share your private key!\n\nIf someone has your private key they will have full control of your wallet! Do not enter this into any websites. Any individual asking for this key is likely a scammer.', tap: 'Tap to reveal your private key', title: 'Your Private Key', }, revealWords: { next: 'I have written these down', subtitle: - 'It is crucial you write all of these\n{{numWords}} words down, in order.\n\nHelium cannot recover these words.', + 'Never give these words to anyone, or enter them into any website. Any person or website asking for these words is likely a scammer. It is crucial you write all of these\n{{numWords}} words down, in order, and keep them safe.\n\nHelium cannot recover these words.', title: 'Your {{numWords}} Word Password', warning: 'Helium cannot recover these words', }, diff --git a/src/utils/linking.ts b/src/utils/linking.ts index 0d9f913a6..5065df2df 100644 --- a/src/utils/linking.ts +++ b/src/utils/linking.ts @@ -26,6 +26,7 @@ export const authenticatedLinking: LinkingOptions = { LinkWallet: 'link_wallet', SignHotspot: 'sign_hotspot', PaymentScreen: 'payment', + DappLoginScreen: 'dapp_login', ImportPrivateKey: 'import_key/:key', }, }, From 728f8271f0c22abe8c302624ae89785d2b4b037f Mon Sep 17 00:00:00 2001 From: Noah Prince <83885631+ChewingGlass@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:01:47 -0500 Subject: [PATCH 20/23] feat(#415): Fix chunking of claims of large #s of hotspots, and add a progress bar (#418) * feat(#415): Add progress bar to bulk claims and fix blockhash expiration * feat(#415): Add progress bar to bulk claims and fix blockhash expiration * feat(#415): Add progress bar to bulk claims and fix blockhash expiration * Feature complete * Add missing file --- src/components/ProgressBar.tsx | 68 ++++++ .../collectables/ClaimAllRewardsScreen.tsx | 65 +++++- .../collectables/ClaimingRewardsScreen.tsx | 23 +- .../collectables/CollectablesTopTabs.tsx | 1 + src/features/collectables/HotspotList.tsx | 8 +- src/hooks/useEntityKey.ts | 13 +- src/hooks/useHntSolConvert.ts | 4 +- src/hooks/useKeyToAsset.ts | 21 ++ src/hooks/useMetaplexMetadata.ts | 9 +- src/hooks/useSubmitTxn.ts | 26 +-- src/locales/en.ts | 6 +- src/navigation/TabBarNavigator.tsx | 1 + src/solana/SolanaProvider.tsx | 38 +++- src/store/slices/solanaSlice.ts | 206 ++++++++++++++++-- src/types/solana.ts | 3 + src/utils/solanaUtils.ts | 100 ++------- 16 files changed, 445 insertions(+), 147 deletions(-) create mode 100644 src/components/ProgressBar.tsx create mode 100644 src/hooks/useKeyToAsset.ts diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx new file mode 100644 index 000000000..bdc2f4710 --- /dev/null +++ b/src/components/ProgressBar.tsx @@ -0,0 +1,68 @@ +import { BoxProps } from '@shopify/restyle' +import { Theme } from '@theme/theme' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { LayoutChangeEvent, LayoutRectangle } from 'react-native' +import { + useAnimatedStyle, + useSharedValue, + withSpring, +} from 'react-native-reanimated' +import { ReAnimatedBox } from './AnimatedBox' +import Box from './Box' + +const ProgressBar = ({ + progress: progressIn, + ...rest +}: BoxProps & { progress: number }) => { + const HEIGHT = 15 + + const [progressRect, setProgressRect] = useState() + + const handleLayout = useCallback((e: LayoutChangeEvent) => { + e.persist() + + setProgressRect(e.nativeEvent.layout) + }, []) + + const PROGRESS_WIDTH = useMemo( + () => (progressRect ? progressRect.width : 0), + [progressRect], + ) + + const width = useSharedValue(0) + + useEffect(() => { + // withRepeat to repeat the animation + width.value = withSpring((progressIn / 100) * PROGRESS_WIDTH) + }, [PROGRESS_WIDTH, width, progressIn]) + + const progress = useAnimatedStyle(() => { + return { + width: width.value, + } + }) + + return ( + + + + + + ) +} + +export default ProgressBar diff --git a/src/features/collectables/ClaimAllRewardsScreen.tsx b/src/features/collectables/ClaimAllRewardsScreen.tsx index 43d03f559..e398de7d6 100644 --- a/src/features/collectables/ClaimAllRewardsScreen.tsx +++ b/src/features/collectables/ClaimAllRewardsScreen.tsx @@ -6,6 +6,14 @@ import CircleLoader from '@components/CircleLoader' import { DelayedFadeIn } from '@components/FadeInOut' import RewardItem from '@components/RewardItem' import Text from '@components/Text' +import { + IOT_MINT, + MOBILE_MINT, + sendAndConfirmWithRetry, + toNumber, +} from '@helium/spl-utils' +import useAlert from '@hooks/useAlert' +import { useHntSolConvert } from '@hooks/useHntSolConvert' import useHotspots from '@hooks/useHotspots' import useSubmitTxn from '@hooks/useSubmitTxn' import { useNavigation } from '@react-navigation/native' @@ -13,9 +21,9 @@ import { IOT_LAZY_KEY, MOBILE_LAZY_KEY } from '@utils/constants' import BN from 'bn.js' import React, { memo, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { IOT_MINT, MOBILE_MINT, toNumber } from '@helium/spl-utils' -import { CollectableNavigationProp } from './collectablesTypes' +import { useSolana } from '../../solana/SolanaProvider' import { BalanceChange } from '../../solana/walletSignBottomSheetTypes' +import { CollectableNavigationProp } from './collectablesTypes' const ClaimAllRewardsScreen = () => { const { t } = useTranslation() @@ -23,6 +31,44 @@ const ClaimAllRewardsScreen = () => { const [redeeming, setRedeeming] = useState(false) const [claimError, setClaimError] = useState() const { submitClaimAllRewards } = useSubmitTxn() + const { + hntEstimateLoading, + hntSolConvertTransaction, + hntEstimate, + hasEnoughSol, + } = useHntSolConvert() + const { showOKCancelAlert } = useAlert() + const { anchorProvider } = useSolana() + const showHNTConversionAlert = useCallback(async () => { + if (!anchorProvider || !hntSolConvertTransaction) return + + const decision = await showOKCancelAlert({ + title: t('browserScreen.insufficientSolToPayForFees'), + message: t('browserScreen.wouldYouLikeToConvert', { + amount: hntEstimate, + ticker: 'HNT', + }), + }) + + if (!decision) return + const signed = await anchorProvider.wallet.signTransaction( + hntSolConvertTransaction, + ) + await sendAndConfirmWithRetry( + anchorProvider.connection, + signed.serialize(), + { + skipPreflight: true, + }, + 'confirmed', + ) + }, [ + anchorProvider, + hntSolConvertTransaction, + showOKCancelAlert, + t, + hntEstimate, + ]) const { hotspots, @@ -45,6 +91,9 @@ const ClaimAllRewardsScreen = () => { try { setClaimError(undefined) setRedeeming(true) + if (!hasEnoughSol) { + await showHNTConversionAlert() + } const balanceChanges: BalanceChange[] = [] @@ -78,11 +127,13 @@ const ClaimAllRewardsScreen = () => { setRedeeming(false) } }, [ - navigation, + hasEnoughSol, + pendingIotRewards, + pendingMobileRewards, submitClaimAllRewards, hotspotsWithMeta, - pendingMobileRewards, - pendingIotRewards, + navigation, + showHNTConversionAlert, ]) const addAllToAccountDisabled = useMemo(() => { @@ -161,7 +212,9 @@ const ClaimAllRewardsScreen = () => { titleColor="black" marginHorizontal="l" onPress={onClaimRewards} - disabled={addAllToAccountDisabled || redeeming} + disabled={ + addAllToAccountDisabled || redeeming || hntEstimateLoading + } TrailingComponent={ redeeming ? ( diff --git a/src/features/collectables/ClaimingRewardsScreen.tsx b/src/features/collectables/ClaimingRewardsScreen.tsx index 4f4fb7784..af7222bc0 100644 --- a/src/features/collectables/ClaimingRewardsScreen.tsx +++ b/src/features/collectables/ClaimingRewardsScreen.tsx @@ -5,6 +5,7 @@ import Box from '@components/Box' import ButtonPressable from '@components/ButtonPressable' import { DelayedFadeIn } from '@components/FadeInOut' import IndeterminateProgressBar from '@components/IndeterminateProgressBar' +import ProgressBar from '@components/ProgressBar' import Text from '@components/Text' import { useSolOwnedAmount } from '@helium/helium-react-hooks' import { useBN } from '@hooks/useBN' @@ -191,7 +192,27 @@ const ClaimingRewardsScreen = () => { {t('collectablesScreen.claimingRewardsBody')} - + {typeof solanaPayment.progress !== 'undefined' ? ( + + + + {solanaPayment.progress.text} + + + ) : ( + + )} )} diff --git a/src/features/collectables/CollectablesTopTabs.tsx b/src/features/collectables/CollectablesTopTabs.tsx index f45a4317f..394cf8c5e 100644 --- a/src/features/collectables/CollectablesTopTabs.tsx +++ b/src/features/collectables/CollectablesTopTabs.tsx @@ -31,6 +31,7 @@ const CollectablesTopTabs = () => { const screenOpts = useCallback( ({ route }: { route: RouteProp }) => ({ + lazy: true, headerShown: false, tabBarLabelStyle: { fontFamily: Font.medium, diff --git a/src/features/collectables/HotspotList.tsx b/src/features/collectables/HotspotList.tsx index f9e90729f..a08c9c5ce 100644 --- a/src/features/collectables/HotspotList.tsx +++ b/src/features/collectables/HotspotList.tsx @@ -183,12 +183,12 @@ const HotspotList = () => { hasPressedState={false} /> diff --git a/src/hooks/useEntityKey.ts b/src/hooks/useEntityKey.ts index 2d3eb26c6..d6e134d46 100644 --- a/src/hooks/useEntityKey.ts +++ b/src/hooks/useEntityKey.ts @@ -1,14 +1,9 @@ -import { useEffect, useState } from 'react' +import { decodeEntityKey } from '@helium/helium-entity-manager-sdk' import { HotspotWithPendingRewards } from '../types/solana' +import { useKeyToAsset } from './useKeyToAsset' export const useEntityKey = (hotspot: HotspotWithPendingRewards) => { - const [entityKey, setEntityKey] = useState() + const { info: kta } = useKeyToAsset(hotspot?.id) - useEffect(() => { - if (hotspot) { - setEntityKey(hotspot.content.json_uri.split('/').slice(-1)[0]) - } - }, [hotspot, setEntityKey]) - - return entityKey + return kta ? decodeEntityKey(kta.entityKey, kta.keySerialization) : undefined } diff --git a/src/hooks/useHntSolConvert.ts b/src/hooks/useHntSolConvert.ts index a352c0a82..4494e5565 100644 --- a/src/hooks/useHntSolConvert.ts +++ b/src/hooks/useHntSolConvert.ts @@ -41,11 +41,11 @@ export function useHntSolConvert() { }, [baseUrl]) const hasEnoughSol = useMemo(() => { - if (!hntBalance || !solBalance || !hntEstimate) return true + if (!hntBalance || !hntEstimate) return true if (hntBalance.lt(hntEstimate)) return true - return solBalance.gt(new BN(0.02 * LAMPORTS_PER_SOL)) + return (solBalance || new BN(0)).gt(new BN(0.02 * LAMPORTS_PER_SOL)) }, [hntBalance, solBalance, hntEstimate]) const { diff --git a/src/hooks/useKeyToAsset.ts b/src/hooks/useKeyToAsset.ts new file mode 100644 index 000000000..354fff239 --- /dev/null +++ b/src/hooks/useKeyToAsset.ts @@ -0,0 +1,21 @@ +import { + mobileInfoKey, + rewardableEntityConfigKey, +} from '@helium/helium-entity-manager-sdk' +import { useAnchorAccount } from '@helium/helium-react-hooks' +import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' +import { MOBILE_SUB_DAO_KEY } from '@utils/constants' + +const type = 'keyToAssetV0' + +export const useKeyToAsset = (entityKey: string | undefined) => { + const [mobileConfigKey] = rewardableEntityConfigKey( + MOBILE_SUB_DAO_KEY, + 'MOBILE', + ) + const [mobileInfo] = mobileInfoKey(mobileConfigKey, entityKey || '') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return useAnchorAccount(mobileInfo, type) +} diff --git a/src/hooks/useMetaplexMetadata.ts b/src/hooks/useMetaplexMetadata.ts index 9e69e9185..bdd965ca2 100644 --- a/src/hooks/useMetaplexMetadata.ts +++ b/src/hooks/useMetaplexMetadata.ts @@ -14,17 +14,16 @@ import { useAsync } from 'react-async-hook' const MPL_PID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s') // eslint-disable-next-line @typescript-eslint/no-explicit-any -const cache: Record = {} +const cache: Record> = {} // eslint-disable-next-line @typescript-eslint/no-explicit-any -async function getMetadata(uri: string | undefined): Promise { +export function getMetadata(uri: string | undefined): Promise { if (uri) { if (!cache[uri]) { - const res = await fetch(uri) - const json = await res.json() - cache[uri] = json + cache[uri] = fetch(uri).then((res) => res.json()) } return cache[uri] } + return Promise.resolve(undefined) } export const METADATA_PARSER: TypedAccountParser = ( diff --git a/src/hooks/useSubmitTxn.ts b/src/hooks/useSubmitTxn.ts index 3bae6ba07..bdbeef351 100644 --- a/src/hooks/useSubmitTxn.ts +++ b/src/hooks/useSubmitTxn.ts @@ -310,33 +310,11 @@ export default () => { throw new Error(t('errors.account')) } - const txns = await solUtils.claimAllRewardsTxns( - anchorProvider, - lazyDistributors, - hotspots, - ) - - const serializedTxs = txns.map((txn) => - txn.serialize({ - requireAllSignatures: false, - }), - ) - - const decision = await walletSignBottomSheetRef.show({ - type: WalletStandardMessageTypes.signTransaction, - url: '', - additionalMessage: t('transactions.signClaimAllRewardsTxn'), - serializedTxs: serializedTxs.map(Buffer.from), - }) - - if (!decision) { - throw new Error('User rejected transaction') - } - dispatch( claimAllRewards({ account: currentAccount, - txns, + lazyDistributors, + hotspots, anchorProvider, cluster, }), diff --git a/src/locales/en.ts b/src/locales/en.ts index 036cee8d2..d024e0c69 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -176,7 +176,7 @@ export default { claimingRewardsBody: 'You can exit this screen while you wait. We’ll update your Wallet momentarily.', claimComplete: 'Rewards Claimed!', - claimCompleteBody: 'We’ve added your tokens to your wallet.', + claimCompleteBody: 'Your tokens have been added to your wallet.', claimError: 'Claim failed. Please try again later.', transferCollectableAlertTitle: 'Are you sure you will like to transfer your collectable?', @@ -219,7 +219,7 @@ export default { 'Warning: Load times may be affected when showing all hotspots per page.', twenty: '20', fifty: '50', - all: 'All', + thousand: '1000', copyEccCompact: 'Copy Hotspot Key', assertLocation: 'Assert Location', antennaSetup: 'Antenna Setup', @@ -332,7 +332,7 @@ export default { chooseTokenToSwap: 'Choose a token to swap', chooseTokenToReceive: 'Choose a token to receive', swapComplete: 'Tokens swapped!', - swapCompleteBody: 'We’ve updated the tokens on your wallet.', + swapCompleteBody: 'The tokens in your wallet have been updated.', swappingTokens: 'Swapping your tokens...', swappingTokensBody: 'You can exit this screen while you wait. We’ll update your Wallet momentarily.', diff --git a/src/navigation/TabBarNavigator.tsx b/src/navigation/TabBarNavigator.tsx index 94716f85f..2134934bc 100644 --- a/src/navigation/TabBarNavigator.tsx +++ b/src/navigation/TabBarNavigator.tsx @@ -201,6 +201,7 @@ const TabBarNavigator = () => { tabBar={(props: BottomTabBarProps) => } screenOptions={{ headerShown: false, + lazy: true, }} sceneContainerStyle={{ paddingBottom: NavBarHeight + bottom, diff --git a/src/solana/SolanaProvider.tsx b/src/solana/SolanaProvider.tsx index 92cdae432..238fd564b 100644 --- a/src/solana/SolanaProvider.tsx +++ b/src/solana/SolanaProvider.tsx @@ -5,7 +5,14 @@ import { init as initHem } from '@helium/helium-entity-manager-sdk' import { init as initHsd } from '@helium/helium-sub-daos-sdk' import { init as initLazy } from '@helium/lazy-distributor-sdk' import { DC_MINT, HNT_MINT } from '@helium/spl-utils' -import { Cluster, Transaction } from '@solana/web3.js' +import { + AccountInfo, + Cluster, + Commitment, + PublicKey, + RpcResponseAndContext, + Transaction, +} from '@solana/web3.js' import React, { ReactNode, createContext, @@ -85,13 +92,40 @@ const useSolanaHook = () => { const cache = useMemo(() => { if (!connection) return - return new AccountFetchCache({ + const c = new AccountFetchCache({ connection, delay: 100, commitment: 'confirmed', missingRefetchDelay: 60 * 1000, extendConnection: true, }) + const oldGetAccountinfoAndContext = + connection.getAccountInfoAndContext.bind(connection) + + // Anchor uses this call on .fetch and .fetchNullable even though it doesn't actually need the context. Add caching. + connection.getAccountInfoAndContext = async ( + publicKey: PublicKey, + com?: Commitment, + ): Promise | null>> => { + if ( + (com || connection.commitment) === 'confirmed' || + typeof (com || connection.commitment) === 'undefined' + ) { + const [result, dispose] = await c.searchAndWatch(publicKey) + setTimeout(dispose, 30 * 1000) // cache for 30s + return { + value: result?.account || null, + context: { + slot: 0, + }, + } + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return oldGetAccountinfoAndContext!(publicKey, com) + } + + return c }, [connection]) useEffect(() => { // Don't sub to hnt or dc they change a bunch diff --git a/src/store/slices/solanaSlice.ts b/src/store/slices/solanaSlice.ts index 75106a7d0..0b0d6dcae 100644 --- a/src/store/slices/solanaSlice.ts +++ b/src/store/slices/solanaSlice.ts @@ -1,22 +1,40 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ import { AnchorProvider } from '@coral-xyz/anchor' +import { + formBulkTransactions, + getBulkRewards, +} from '@helium/distributor-oracle' +import { + decodeEntityKey, + init, + keyToAssetForAsset, +} from '@helium/helium-entity-manager-sdk' +import * as lz from '@helium/lazy-distributor-sdk' import { bulkSendRawTransactions, bulkSendTransactions, + chunks, sendAndConfirmWithRetry, } from '@helium/spl-utils' import { + PayloadAction, SerializedError, createAsyncThunk, createSlice, } from '@reduxjs/toolkit' import { Cluster, + PublicKey, SignaturesForAddressOptions, Transaction, } from '@solana/web3.js' +import BN from 'bn.js' +import bs58 from 'bs58' import { first, last } from 'lodash' import { CSAccount } from '../../storage/cloudStorage' import { Activity } from '../../types/activity' +import { HotspotWithPendingRewards } from '../../types/solana' import * as Logger from '../../utils/logger' import * as solUtils from '../../utils/solanaUtils' import { postPayment } from '../../utils/walletApiV2' @@ -38,6 +56,7 @@ export type SolanaState = { error?: SerializedError success?: boolean signature?: string + progress?: { percent: number; text: string } // 0-100 } activity: { loading?: boolean @@ -80,7 +99,8 @@ type ClaimRewardInput = { type ClaimAllRewardsInput = { account: CSAccount - txns: Transaction[] + lazyDistributors: PublicKey[] + hotspots: HotspotWithPendingRewards[] anchorProvider: AnchorProvider cluster: Cluster } @@ -254,12 +274,7 @@ export const claimRewards = createAsyncThunk( { dispatch }, ) => { try { - const signed = await anchorProvider.wallet.signAllTransactions(txns) - - const signatures = await bulkSendRawTransactions( - anchorProvider.connection, - signed.map((s) => s.serialize()), - ) + const signatures = await bulkSendTransactions(anchorProvider, txns) postPayment({ signatures, cluster }) @@ -276,20 +291,170 @@ export const claimRewards = createAsyncThunk( }, ) +const CHUNK_SIZE = 25 export const claimAllRewards = createAsyncThunk( 'solana/claimAllRewards', async ( - { account, anchorProvider, cluster, txns }: ClaimAllRewardsInput, + { + account, + anchorProvider, + cluster, + lazyDistributors, + hotspots, + }: ClaimAllRewardsInput, { dispatch }, ) => { try { - const signed = await anchorProvider.wallet.signAllTransactions(txns) - - // eslint-disable-next-line no-await-in-loop - await bulkSendRawTransactions( - anchorProvider.connection, - signed.map((s) => s.serialize()), + const ret: string[] = [] + let triesRemaining = 10 + const program = await lz.init(anchorProvider) + const hemProgram = await init(anchorProvider) + + const mints = await Promise.all( + lazyDistributors.map(async (d) => { + return (await program.account.lazyDistributorV0.fetch(d)).rewardsMint + }), + ) + const ldToMint = lazyDistributors.reduce((acc, ld, index) => { + acc[ld.toBase58()] = mints[index] + return acc + }, {} as Record) + // One tx per hotspot per mint/lazy dist + const totalTxns = hotspots.reduce((acc, hotspot) => { + mints.forEach((mint) => { + if ( + hotspot.pendingRewards && + hotspot.pendingRewards[mint.toString()] && + new BN(hotspot.pendingRewards[mint.toString()]).gt(new BN(0)) + ) + acc += 1 + }) + return acc + }, 0) + dispatch( + solanaSlice.actions.setPaymentProgress({ + percent: 0, + text: 'Preparing transactions...', + }), ) + for (const lazyDistributor of lazyDistributors) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const mint = ldToMint[lazyDistributor.toBase58()]! + const hotspotsWithRewards = hotspots.filter( + (hotspot) => + hotspot.pendingRewards && + new BN(hotspot.pendingRewards[mint.toBase58()]).gt(new BN(0)), + ) + for (let chunk of chunks(hotspotsWithRewards, CHUNK_SIZE)) { + const thisRet: string[] = [] + // Continually send in bulk while resetting blockhash until we send them all + // eslint-disable-next-line no-constant-condition + while (true) { + dispatch( + solanaSlice.actions.setPaymentProgress({ + percent: ((ret.length + thisRet.length) * 100) / totalTxns, + text: `Preparing batch of ${chunk.length} transactions.\n${ + totalTxns - ret.length + } total transactions remaining.`, + }), + ) + const recentBlockhash = + // eslint-disable-next-line no-await-in-loop + await anchorProvider.connection.getLatestBlockhash('confirmed') + + const keyToAssets = chunk.map((h) => + keyToAssetForAsset(solUtils.toAsset(h)), + ) + const ktaAccs = await Promise.all( + keyToAssets.map((kta) => + hemProgram.account.keyToAssetV0.fetch(kta), + ), + ) + const entityKeys = ktaAccs.map( + (kta) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + decodeEntityKey(kta.entityKey, kta.keySerialization)!, + ) + + const rewards = await getBulkRewards( + program, + lazyDistributor, + entityKeys, + ) + dispatch( + solanaSlice.actions.setPaymentProgress({ + percent: ((ret.length + thisRet.length) * 100) / totalTxns, + text: `Sending batch of ${chunk.length} transactions.\n${ + totalTxns - ret.length + } total transactions remaining.`, + }), + ) + + const txns = await formBulkTransactions({ + program, + rewards, + assets: chunk.map((h) => new PublicKey(h.id)), + compressionAssetAccs: chunk.map(solUtils.toAsset), + lazyDistributor, + assetEndpoint: anchorProvider.connection.rpcEndpoint, + wallet: anchorProvider.wallet.publicKey, + }) + const signedTxs = await anchorProvider.wallet.signAllTransactions( + txns, + ) + // eslint-disable-next-line @typescript-eslint/no-loop-func + const txsWithSigs = signedTxs.map((tx, index) => { + return { + transaction: chunk[index], + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sig: bs58.encode(tx.signatures[0]!.signature!), + } + }) + // eslint-disable-next-line no-await-in-loop + const confirmedTxs = await bulkSendRawTransactions( + anchorProvider.connection, + signedTxs.map((s) => s.serialize()), + ({ totalProgress }) => + dispatch( + solanaSlice.actions.setPaymentProgress({ + percent: + ((totalProgress + ret.length + thisRet.length) * 100) / + totalTxns, + text: `Confiming ${txns.length - totalProgress}/${ + txns.length + } transactions.\n${ + totalTxns - ret.length - thisRet.length + } total transactions remaining`, + }), + ), + recentBlockhash.lastValidBlockHeight, + // Hail mary, try with preflight enabled. Sometimes this causes + // errors that wouldn't otherwise happen + triesRemaining !== 1, + ) + thisRet.push(...confirmedTxs) + if (confirmedTxs.length === signedTxs.length) { + break + } + + const retSet = new Set(thisRet) + + chunk = txsWithSigs + .filter(({ sig }) => !retSet.has(sig)) + .map(({ transaction }) => transaction) + + triesRemaining -= 1 + if (triesRemaining <= 0) { + throw new Error( + `Failed to submit all txs after blockhashes expired, ${ + signedTxs.length - confirmedTxs.length + } remain`, + ) + } + } + ret.push(...thisRet) + } + } // If the claim is successful, we need to update the hotspots so pending rewards are updated. dispatch(fetchHotspots({ account, anchorProvider, cluster })) @@ -388,12 +553,21 @@ const solanaSlice = createSlice({ name: 'solana', initialState, reducers: { + setPaymentProgress: ( + state, + action: PayloadAction<{ percent: number; text: string }>, + ) => { + if (state.payment) { + state.payment.progress = action.payload + } + }, resetPayment: (state) => { state.payment = { success: false, loading: false, error: undefined, signature: undefined, + progress: undefined, } }, }, @@ -429,6 +603,7 @@ const solanaSlice = createSlice({ success: true, loading: false, error: undefined, + progress: undefined, } }) builder.addCase(claimRewards.rejected, (state, action) => { @@ -437,6 +612,7 @@ const solanaSlice = createSlice({ loading: false, error: action.error, signature: undefined, + progress: undefined, } }) builder.addCase(claimRewards.pending, (state, _action) => { @@ -445,6 +621,7 @@ const solanaSlice = createSlice({ loading: true, error: undefined, signature: undefined, + progress: undefined, } }) builder.addCase(claimRewards.fulfilled, (state, _action) => { @@ -454,6 +631,7 @@ const solanaSlice = createSlice({ loading: false, error: undefined, signature: signatures[0], + progress: undefined, } }) builder.addCase(sendAnchorTxn.rejected, (state, action) => { diff --git a/src/types/solana.ts b/src/types/solana.ts index 02d78188f..0d3efdd89 100644 --- a/src/types/solana.ts +++ b/src/types/solana.ts @@ -10,12 +10,15 @@ import { init as initHsd } from '@helium/helium-sub-daos-sdk' import { init as initDc } from '@helium/data-credits-sdk' import { init as initHem } from '@helium/helium-entity-manager-sdk' import { init as initLazy } from '@helium/lazy-distributor-sdk' +import { BulkRewards } from '@helium/distributor-oracle' import { TokenAmount } from '@solana/web3.js' import { Creator } from '@metaplex-foundation/mpl-bubblegum' export type HotspotWithPendingRewards = CompressedNFT & { // mint id to pending rewards pendingRewards: Record | undefined + // mint id to rewards + rewards: Record | undefined } export type HemProgram = Awaited> diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 6a943279a..63c78af73 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -5,20 +5,19 @@ import { delegatedDataCreditsKey, escrowAccountKey, } from '@helium/data-credits-sdk' -import { - formBulkTransactions, - getBulkRewards, - getPendingRewards, -} from '@helium/distributor-oracle' +import { getPendingRewards } from '@helium/distributor-oracle' import { PROGRAM_ID as FanoutProgramId, fanoutKey, membershipCollectionKey, } from '@helium/fanout-sdk' import { + decodeEntityKey, entityCreatorKey, + init, init as initHem, iotInfoKey, + keyToAssetForAsset, keyToAssetKey, mobileInfoKey, rewardableEntityConfigKey, @@ -38,7 +37,6 @@ import { searchAssets, sendAndConfirmWithRetry, toBN, - truthy, } from '@helium/spl-utils' import * as tm from '@helium/treasury-management-sdk' import { @@ -46,7 +44,11 @@ import { registrarCollectionKey, registrarKey, } from '@helium/voter-stake-registry-sdk' -import { METADATA_PARSER, getMetadataId } from '@hooks/useMetaplexMetadata' +import { + METADATA_PARSER, + getMetadata, + getMetadataId, +} from '@hooks/useMetaplexMetadata' import { JsonMetadata, Metadata, Metaplex } from '@metaplex-foundation/js' import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, @@ -1059,9 +1061,7 @@ export const getCompressedNFTMetadata = async ( const collectablesWithMetadata = await Promise.all( collectables.map(async (col) => { try { - const { data } = await axios.get(col.content.json_uri, { - timeout: 3000, - }) + const { data } = await getMetadata(col.content.json_uri) return { ...col, content: { @@ -1091,10 +1091,19 @@ export async function annotateWithPendingRewards( hotspots: CompressedNFT[], ): Promise { const program = await lz.init(provider) + const hemProgram = await init(provider) const dao = DAO_KEY - const entityKeys = hotspots.map((h) => { - return h.content.json_uri.split('/').slice(-1)[0] - }) + const keyToAssets = hotspots.map((h) => + keyToAssetForAsset(toAsset(h as CompressedNFT)), + ) + const ktaAccs = await Promise.all( + keyToAssets.map((kta) => hemProgram.account.keyToAssetV0.fetch(kta)), + ) + const entityKeys = ktaAccs.map( + (kta) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + decodeEntityKey(kta.entityKey, kta.keySerialization)!, + ) const mobileRewards = await getPendingRewards( program, @@ -1695,12 +1704,7 @@ export const updateEntityInfoTxn = async ({ } } -const chunks = (array: T[], size: number): T[][] => - Array.apply(0, new Array(Math.ceil(array.length / size))).map((_, index) => - array.slice(index * size, (index + 1) * size), - ) - -function toAsset(hotspot: HotspotWithPendingRewards): Asset { +export function toAsset(hotspot: CompressedNFT): Asset { return { ...hotspot, id: new PublicKey(hotspot.id), @@ -1726,61 +1730,3 @@ function toAsset(hotspot: HotspotWithPendingRewards): Asset { }, } } - -export async function claimAllRewardsTxns( - anchorProvider: AnchorProvider, - lazyDistributors: PublicKey[], - hotspots: HotspotWithPendingRewards[], -) { - try { - const { connection } = anchorProvider - const { publicKey: payer } = anchorProvider.wallet - const lazyProgram = await lz.init(anchorProvider) - let txns: Transaction[] | undefined - - // Use for loops to linearly order promises - // eslint-disable-next-line no-restricted-syntax - for (const lazyDistributor of lazyDistributors) { - const lazyDistributorAcc = - // eslint-disable-next-line no-await-in-loop - await lazyProgram.account.lazyDistributorV0.fetch(lazyDistributor) - // eslint-disable-next-line no-restricted-syntax - for (const chunk of chunks(hotspots, 25)) { - const entityKeys = chunk.map( - (h) => h.content.json_uri.split('/').slice(-1)[0], - ) - - // eslint-disable-next-line no-await-in-loop - const rewards = await getBulkRewards( - lazyProgram, - lazyDistributor, - entityKeys, - ) - - // eslint-disable-next-line no-await-in-loop - const txs = await formBulkTransactions({ - program: lazyProgram, - rewards, - assets: chunk.map((h) => new PublicKey(h.id)), - compressionAssetAccs: chunk.map(toAsset), - lazyDistributor, - lazyDistributorAcc, - assetEndpoint: connection.rpcEndpoint, - wallet: payer, - }) - - const validTxns = txs.filter(truthy) - txns = [...(txns || []), ...validTxns] - } - } - - if (!txns) { - throw new Error('Unable to form transactions') - } - - return txns - } catch (e) { - Logger.error(e) - throw e as Error - } -} From ce56c31bdecb40e8c1b99eb6c0563dffb42dc9bc Mon Sep 17 00:00:00 2001 From: Bryan Date: Thu, 10 Aug 2023 09:02:17 -0500 Subject: [PATCH 21/23] Bugfix/gain height ux (#417) * Asserting with gain and height + dismissable keyboard * Setup Antenna * PR feedback * Add setting up antenna screen --- ios/Podfile.lock | 8 +- .../collectables/AntennaSetupScreen.tsx | 206 ++++++++++++++++++ .../collectables/AssertLocationScreen.tsx | 169 +++++++------- .../collectables/CollectablesNavigator.tsx | 10 + .../collectables/HotspotDetailsScreen.tsx | 30 ++- .../collectables/SettingUpAntennaScreen.tsx | 196 +++++++++++++++++ .../collectables/collectablesTypes.ts | 7 +- src/hooks/useIotInfo.ts | 15 +- src/hooks/useMobileInfo.ts | 15 +- src/locales/en.ts | 15 ++ 10 files changed, 564 insertions(+), 107 deletions(-) create mode 100644 src/features/collectables/AntennaSetupScreen.tsx create mode 100644 src/features/collectables/SettingUpAntennaScreen.tsx diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a85de240a..109a1773f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -841,7 +841,7 @@ SPEC CHECKSUMS: BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 34a999fda29036898a09c6a6b728b0b4189e1a44 - Charts: ce0768268078eee0336f122c3c4ca248e4e204c5 + Charts: 354f86803d11d9c35de280587fef50d1af063978 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 EXBarCodeScanner: 8e23fae8d267dbef9f04817833a494200f1fce35 @@ -861,9 +861,9 @@ SPEC CHECKSUMS: FBLazyVector: f1897022b53abf1469d6ad692ee2c69f57d967f3 FBReactNativeSpec: 627fd07f1b9d498c9fa572e76d7f1a6b1ee9a444 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 791fe035093b84822da7f0870421a25839ca7870 helium-react-native-sdk: 32c0a7e3abc733a7f3d291013b2db31475fc6980 - hermes-engine: 0784cadad14b011580615c496f77e0ae112eed75 + hermes-engine: 7a53ccac09146018a08239c5425625fdb79a6162 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 MapboxCommon: fdf7fd31c90b7b607cd9c63e37797f023c01d860 MapboxCoreMaps: 24270c7c6b8cb71819fc2f3c549db9620ee4d019 @@ -871,7 +871,7 @@ SPEC CHECKSUMS: MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d OneSignalXCFramework: 81ceac017a290f23793443323090cfbe888f74ea - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 85766c3226c7ec638f05ad7cb3cf6a268d6c4241 RCTRequired: bd6045fbd511da5efe6db89eecb21e4e36bd7cbf RCTTypeSafety: c06d9f906faa69dd1c88223204c3a24767725fd8 React: b9ea33557ef1372af247f95d110fbdea114ed3b2 diff --git a/src/features/collectables/AntennaSetupScreen.tsx b/src/features/collectables/AntennaSetupScreen.tsx new file mode 100644 index 000000000..cd9911835 --- /dev/null +++ b/src/features/collectables/AntennaSetupScreen.tsx @@ -0,0 +1,206 @@ +import React, { useEffect, useState, useMemo, useCallback, memo } from 'react' +import BackScreen from '@components/BackScreen' +import { ReAnimatedBox } from '@components/AnimatedBox' +import Box from '@components/Box' +import ButtonPressable from '@components/ButtonPressable' +import CircleLoader from '@components/CircleLoader' +import SafeAreaBox from '@components/SafeAreaBox' +import Text from '@components/Text' +import TextInput from '@components/TextInput' +import useSubmitTxn from '@hooks/useSubmitTxn' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' +import { useTranslation } from 'react-i18next' +import { + KeyboardAvoidingView, + Keyboard, + TouchableWithoutFeedback, +} from 'react-native' +import { useEntityKey } from '@hooks/useEntityKey' +import { useIotInfo } from '@hooks/useIotInfo' +import { Edge } from 'react-native-safe-area-context' +import { DelayedFadeIn } from '@components/FadeInOut' +import { + CollectableNavigationProp, + CollectableStackParamList, +} from './collectablesTypes' +import { parseH3BNLocation } from '../../utils/h3' +import * as Logger from '../../utils/logger' + +const BUTTON_HEIGHT = 65 +type Route = RouteProp +const AntennaSetupScreen = () => { + const { t } = useTranslation() + const nav = useNavigation() + const route = useRoute() + const { collectable } = route.params + const entityKey = useEntityKey(collectable) + const iotInfoAcc = useIotInfo(entityKey) + const safeEdges = useMemo(() => ['bottom'] as Edge[], []) + const backEdges = useMemo(() => ['top'] as Edge[], []) + const [hasSetDefaults, setHasSetDefaults] = useState(false) + const [gain, setGain] = useState() + const [elevation, setElevation] = useState() + const [updating, setUpdating] = useState(false) + const [transactionError, setTransactionError] = useState() + const { submitUpdateEntityInfo } = useSubmitTxn() + + const iotLocation = useMemo(() => { + if (!iotInfoAcc?.info?.location) { + return undefined + } + + return parseH3BNLocation(iotInfoAcc.info.location).reverse() + }, [iotInfoAcc]) + + useEffect(() => { + if (!hasSetDefaults && iotInfoAcc?.info) { + if (iotInfoAcc?.info?.gain) { + setGain(`${iotInfoAcc?.info?.gain / 10}`) + } + + if (iotInfoAcc?.info?.elevation) { + setElevation(`${iotInfoAcc?.info?.elevation}`) + } + + setHasSetDefaults(true) + } + }, [iotInfoAcc, setGain, setElevation, hasSetDefaults, setHasSetDefaults]) + + const handleUpdateElevGain = useCallback(async () => { + if (iotLocation && entityKey) { + setTransactionError(undefined) + setUpdating(true) + try { + await submitUpdateEntityInfo({ + type: 'iot', + entityKey, + lng: iotLocation[0], + lat: iotLocation[1], + elevation, + decimalGain: gain, + }) + nav.push('SettingUpAntennaScreen') + } catch (error) { + setUpdating(false) + Logger.error(error) + setTransactionError((error as Error).message) + } + } + }, [ + iotLocation, + entityKey, + elevation, + gain, + setUpdating, + setTransactionError, + submitUpdateEntityInfo, + nav, + ]) + + const showError = useMemo(() => { + if (transactionError) return transactionError + }, [transactionError]) + + return ( + + + Keyboard.dismiss()}> + + + + + {t('antennaSetupScreen.antennaSetup')} + + + {t('antennaSetupScreen.antennaSetupDescription')} + + + + + + + + + {showError && ( + + {showError} + + )} + + + + ) : undefined + } + /> + + + + + + + ) +} + +export default memo(AntennaSetupScreen) diff --git a/src/features/collectables/AssertLocationScreen.tsx b/src/features/collectables/AssertLocationScreen.tsx index 16b6aabc0..a0ab3ce7e 100644 --- a/src/features/collectables/AssertLocationScreen.tsx +++ b/src/features/collectables/AssertLocationScreen.tsx @@ -30,7 +30,12 @@ import React, { useRef, } from 'react' import { useTranslation } from 'react-i18next' -import { Alert, KeyboardAvoidingView } from 'react-native' +import { + Alert, + KeyboardAvoidingView, + Keyboard, + TouchableWithoutFeedback, +} from 'react-native' import { Config } from 'react-native-config' import { Edge } from 'react-native-safe-area-context' import 'text-encoding-polyfill' @@ -141,14 +146,16 @@ const AssertLocationScreen = () => { ]) useEffect(() => { - if (iotInfoAcc?.info?.gain) { - setGain(`${iotInfoAcc?.info?.gain / 10}`) - } + if (!elevGainVisible) { + if (iotInfoAcc?.info?.gain) { + setGain(`${iotInfoAcc?.info?.gain / 10}`) + } - if (iotInfoAcc?.info?.elevation) { - setElevation(`${iotInfoAcc?.info?.elevation}`) + if (iotInfoAcc?.info?.elevation) { + setElevation(`${iotInfoAcc?.info?.elevation}`) + } } - }, [iotInfoAcc, setGain, setElevation]) + }, [iotInfoAcc, elevGainVisible, setGain, setElevation]) const resetGain = useCallback( () => @@ -551,7 +558,7 @@ const AssertLocationScreen = () => { } TrailingComponent={ asserting ? ( - + ) : undefined } /> @@ -574,78 +581,86 @@ const AssertLocationScreen = () => { edges={backEdges} onClose={hideElevGain} > - - - - - {t('assertLocationScreen.antennaSetup')} - - - {t('assertLocationScreen.antennaSetupDescription')} - - - setGain(val), - multiline: true, - value: gain, - returnKeyType: 'next', - keyboardType: 'decimal-pad', - }} - /> - - Keyboard.dismiss()}> + + + + + {t('assertLocationScreen.antennaSetup')} + + + {t('assertLocationScreen.antennaSetupDescription')} + + + + + setElevation(val), - value: elevation, - keyboardType: 'decimal-pad', - }} + )}`} + textInputProps={{ + placeholder: t( + 'assertLocationScreen.elevationPlaceholder', + ), + onChangeText: setElevation, + value: elevation, + keyboardType: 'decimal-pad', + }} + /> + + + + - - - - - - + + + ) : undefined} diff --git a/src/features/collectables/CollectablesNavigator.tsx b/src/features/collectables/CollectablesNavigator.tsx index 5d403de6d..104becc5c 100644 --- a/src/features/collectables/CollectablesNavigator.tsx +++ b/src/features/collectables/CollectablesNavigator.tsx @@ -19,6 +19,8 @@ import ClaimAllRewardsScreen from './ClaimAllRewardsScreen' import ClaimingRewardsScreen from './ClaimingRewardsScreen' import CollectionScreen from './CollectionScreen' import NftDetailsScreen from './NftDetailsScreen' +import AntennaSetupScreen from './AntennaSetupScreen' +import SettingUpAntennaScreen from './SettingUpAntennaScreen' const CollectablesStack = createStackNavigator() @@ -41,6 +43,14 @@ const CollectablesStackScreen = () => { name="AssertLocationScreen" component={AssertLocationScreen} /> + + { const copyText = useCopyText() const { collectable } = route.params + const entityKey = useEntityKey(collectable) + const iotInfoAcc = useIotInfo(entityKey) + const pendingIotRewards = collectable && collectable.pendingRewards && @@ -75,6 +80,13 @@ const HotspotDetailsScreen = () => { }) }, [collectable, navigation]) + const handleAntennaSetup = useCallback(() => { + setOptionsOpen(false) + navigation.navigate('AntennaSetupScreen', { + collectable, + }) + }, [collectable, navigation]) + const handleClaimRewards = useCallback(() => { navigation.navigate('ClaimRewardsScreen', { hotspot: collectable, @@ -123,6 +135,15 @@ const HotspotDetailsScreen = () => { selected={false} hasPressedState={false} /> + {iotInfoAcc?.info?.location && ( + + )} { /> ), - [handleSend, handleAssertLocation, handleCopyAddress, t], + [ + handleSend, + handleAssertLocation, + handleAntennaSetup, + handleCopyAddress, + iotInfoAcc, + t, + ], ) return ( diff --git a/src/features/collectables/SettingUpAntennaScreen.tsx b/src/features/collectables/SettingUpAntennaScreen.tsx new file mode 100644 index 000000000..b43693ee2 --- /dev/null +++ b/src/features/collectables/SettingUpAntennaScreen.tsx @@ -0,0 +1,196 @@ +import React, { memo, useCallback } from 'react' +import Box from '@components/Box' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { useNavigation } from '@react-navigation/native' +import { TabBarNavigationProp } from 'src/navigation/rootTypes' +import { ReAnimatedBox } from '@components/AnimatedBox' +import { DelayedFadeIn } from '@components/FadeInOut' +import BackArrow from '@assets/images/backArrow.svg' +import AccountIcon from '@components/AccountIcon' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' +import Text from '@components/Text' +import ButtonPressable from '@components/ButtonPressable' +import IndeterminateProgressBar from '@components/IndeterminateProgressBar' +import { parseTransactionError } from '@utils/solanaUtils' +import { useBN } from '@hooks/useBN' +import { useCurrentWallet } from '@hooks/useCurrentWallet' +import { useSolOwnedAmount } from '@helium/helium-react-hooks' +import { RootState } from '../../store/rootReducer' + +const SettingUpAntennaScreen = () => { + const { currentAccount } = useAccountStorage() + const navigation = useNavigation() + const wallet = useCurrentWallet() + const solBalance = useBN(useSolOwnedAmount(wallet).amount) + const { bottom } = useSafeAreaInsets() + + const { t } = useTranslation() + const solanaPayment = useSelector( + (reduxState: RootState) => reduxState.solana.payment, + ) + + const onReturn = useCallback(() => { + // Reset Collectables stack to first screen + navigation.reset({ + index: 0, + routes: [{ name: 'Collectables' }], + }) + }, [navigation]) + + if (!currentAccount) { + return null + } + + return ( + + + + + + + {solanaPayment && !solanaPayment.error && !solanaPayment.loading && ( + + + {t('antennaSetupScreen.settingUpComplete')} + + + {t('antennaSetupScreen.settingUpCompleteBody')} + + + )} + + {solanaPayment?.error && ( + + + {t('collectablesScreen.rewardsError')} + + + {parseTransactionError( + solBalance, + solanaPayment?.error?.message, + )} + + + )} + + {!solanaPayment && ( + + + {t('antennaSetupScreen.settingUpError')} + + + )} + + {solanaPayment && solanaPayment.loading && ( + + + {t('antennaSetupScreen.settingUp')} + + + {t('antennaSetupScreen.settingUpBody')} + + + + + + )} + + + + } + /> + + + + ) +} + +export default memo(SettingUpAntennaScreen) diff --git a/src/features/collectables/collectablesTypes.ts b/src/features/collectables/collectablesTypes.ts index 19a499c71..0b4e65a8b 100644 --- a/src/features/collectables/collectablesTypes.ts +++ b/src/features/collectables/collectablesTypes.ts @@ -18,14 +18,16 @@ export type CollectableStackParamList = { AssertLocationScreen: { collectable: HotspotWithPendingRewards } + AntennaSetupScreen: { + collectable: HotspotWithPendingRewards + } + SettingUpAntennaScreen: undefined PaymentScreen: undefined | PaymentRouteParam - ClaimRewardsScreen: { hotspot: HotspotWithPendingRewards } ClaimAllRewardsScreen: undefined ClaimingRewardsScreen: undefined - CollectionScreen: { collection: Collectable[] } @@ -41,7 +43,6 @@ export type CollectableStackParamList = { TransferCompleteScreen: { collectable: CompressedNFT | Collectable } - AddNewContact: undefined PaymentQrScanner: undefined AddressBookNavigator: undefined diff --git a/src/hooks/useIotInfo.ts b/src/hooks/useIotInfo.ts index f067aa22a..4a605a942 100644 --- a/src/hooks/useIotInfo.ts +++ b/src/hooks/useIotInfo.ts @@ -1,11 +1,9 @@ import { IdlAccounts } from '@coral-xyz/anchor' -import { UseAccountState } from '@helium/account-fetch-cache-hooks' import { iotInfoKey, rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' -import { useIdlAccount } from '@helium/helium-react-hooks' -import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' +import { useAnchorAccount } from '@helium/helium-react-hooks' import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' import { PublicKey } from '@solana/web3.js' import { IOT_SUB_DAO_KEY } from '@utils/constants' @@ -16,16 +14,11 @@ export type IotHotspotInfoV0 = pubKey: PublicKey } -export const useIotInfo = ( - entityKey: string | undefined, -): UseAccountState | undefined => { +export const useIotInfo = (entityKey: string | undefined) => { const [iotConfigKey] = rewardableEntityConfigKey(IOT_SUB_DAO_KEY, 'IOT') const [iotInfo] = iotInfoKey(iotConfigKey, entityKey || '') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return useIdlAccount( - iotInfo, - IDL as HeliumEntityManager, - type, - ) + return useAnchorAccount(iotInfo, type) } diff --git a/src/hooks/useMobileInfo.ts b/src/hooks/useMobileInfo.ts index 4cdd65706..40272d8d5 100644 --- a/src/hooks/useMobileInfo.ts +++ b/src/hooks/useMobileInfo.ts @@ -1,11 +1,9 @@ import { IdlAccounts } from '@coral-xyz/anchor' -import { UseAccountState } from '@helium/account-fetch-cache-hooks' import { mobileInfoKey, rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' -import { useIdlAccount } from '@helium/helium-react-hooks' -import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' +import { useAnchorAccount } from '@helium/helium-react-hooks' import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' import { PublicKey } from '@solana/web3.js' import { MOBILE_SUB_DAO_KEY } from '@utils/constants' @@ -16,19 +14,14 @@ export type MobileHotspotInfoV0 = pubKey: PublicKey } -export const useMobileInfo = ( - entityKey: string | undefined, -): UseAccountState | undefined => { +export const useMobileInfo = (entityKey: string | undefined) => { const [mobileConfigKey] = rewardableEntityConfigKey( MOBILE_SUB_DAO_KEY, 'MOBILE', ) const [mobileInfo] = mobileInfoKey(mobileConfigKey, entityKey || '') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return useIdlAccount( - mobileInfo, - IDL as HeliumEntityManager, - type, - ) + return useAnchorAccount(mobileInfo, type) } diff --git a/src/locales/en.ts b/src/locales/en.ts index d024e0c69..db630f4d0 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -324,6 +324,21 @@ export default { locationNotFound: 'Location not found, Please try again.', mobileTitle: 'MOBILE', }, + antennaSetupScreen: { + title: 'Antenna Setup', + antennaSetup: 'Antenna Setup', + antennaSetupDescription: + 'Submit gain and elevation details for your Hotspot', + gainPlaceholder: 'TX / RX Gain (dBi)', + elevationPlaceholder: 'Elevation (meters)', + submit: 'Update Antenna', + settingUp: 'Setting up your antenna...', + settingUpBody: 'Please wait while we update your Antenna!', + settingUpError: 'Antenna Setup failed. Please try again later.', + settingUpComplete: 'Antenna Setup!', + settingUpCompleteBody: + 'We’ve updated the gain and elevation of your antenna.', + }, swapsScreen: { title: 'Swap my Tokens', swapTokens: 'Swap Tokens', From b49267960752f098bb52e55dd93a88074c577a09 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 10 Aug 2023 09:08:42 -0500 Subject: [PATCH 22/23] Bump versions --- ios/HeliumWallet.xcodeproj/project.pbxproj | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/HeliumWallet.xcodeproj/project.pbxproj b/ios/HeliumWallet.xcodeproj/project.pbxproj index bae96ed6a..cd15b69e5 100644 --- a/ios/HeliumWallet.xcodeproj/project.pbxproj +++ b/ios/HeliumWallet.xcodeproj/project.pbxproj @@ -997,7 +997,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2.0; ONLY_ACTIVE_ARCH = NO; OTHER_LDFLAGS = ( "$(inherited)", @@ -1035,7 +1035,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1203,7 +1203,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -1248,7 +1248,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.helium.wallet.app.OneSignalNotificationServiceExtension; @@ -1296,7 +1296,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -1345,7 +1345,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.helium.wallet.app.HeliumWalletWidget; diff --git a/package.json b/package.json index 18e85d485..2a84d3419 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "helium-wallet", - "version": "2.1.1", + "version": "2.2.0", "private": true, "scripts": { "postinstall": "patch-package && ./node_modules/.bin/rn-nodeify --hack --install && npx jetify", From 8a52aefc14155518a4ea90e622e23f53b3f8b91a Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Thu, 10 Aug 2023 09:11:59 -0500 Subject: [PATCH 23/23] Review comments --- src/components/TokenIcon.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx index 8b6d0796a..bb562664e 100644 --- a/src/components/TokenIcon.tsx +++ b/src/components/TokenIcon.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Image } from 'react-native' +import Box from './Box' type Props = { size?: number @@ -19,7 +20,14 @@ const TokenIcon = ({ size = 40, img }: Props) => { ) } - return null + return ( + + ) } export default TokenIcon