diff --git a/packages/@core-js/src/Tonkeeper.ts b/packages/@core-js/src/Tonkeeper.ts index 1ac92fb8e..06ebaf55b 100644 --- a/packages/@core-js/src/Tonkeeper.ts +++ b/packages/@core-js/src/Tonkeeper.ts @@ -59,7 +59,13 @@ export class Tonkeeper { // TODO: for temp, rewrite it when ton wallet will it be moved here; // init() must be called when app starts - public async init(address: string, isTestnet: boolean, tronAddress: string) { + public async init( + address: string, + isTestnet: boolean, + tronAddress: string, + // TODO: remove after transition to UQ address format + bounceable = true, + ) { try { this.destroy(); if (address) { @@ -75,6 +81,7 @@ export class Tonkeeper { network: isTestnet ? WalletNetwork.testnet : WalletNetwork.mainnet, tronAddress: tronAddress, address: address, + bounceable, }, ); diff --git a/packages/@core-js/src/Wallet.ts b/packages/@core-js/src/Wallet.ts index ed97558eb..4b8de9404 100644 --- a/packages/@core-js/src/Wallet.ts +++ b/packages/@core-js/src/Wallet.ts @@ -106,7 +106,9 @@ export class Wallet { network: walletInfo.network, }; - const tonAddresses = Address.parse(walletInfo.address).toAll({ + const tonAddresses = Address.parse(walletInfo.address, { + bounceable: walletInfo.bounceable, + }).toAll({ testOnly: walletInfo.network === WalletNetwork.testnet, }); diff --git a/packages/@core-js/src/formatters/Address.ts b/packages/@core-js/src/formatters/Address.ts index f1ab4420a..8d8632a1b 100644 --- a/packages/@core-js/src/formatters/Address.ts +++ b/packages/@core-js/src/formatters/Address.ts @@ -35,7 +35,7 @@ export class Address { private address: AddressType; constructor(source: string, formatOptions: Partial = {}) { - this.formatOptions = Object.assign(defaultFormatOptions, formatOptions); + this.formatOptions = { ...defaultFormatOptions, ...formatOptions }; this.address = new TonWeb.Address(source); } @@ -137,14 +137,12 @@ export class Address { } private mergeOptions(options: FormatOptions = {}): Required { - return Object.assign( - { - bounceable: this.getFormatOption(this.formatOptions.bounceable), - testOnly: this.getFormatOption(this.formatOptions.testOnly), - urlSafe: this.getFormatOption(this.formatOptions.urlSafe), - }, - options, - ); + return { + bounceable: this.getFormatOption(this.formatOptions.bounceable), + testOnly: this.getFormatOption(this.formatOptions.testOnly), + urlSafe: this.getFormatOption(this.formatOptions.urlSafe), + ...options, + }; } private getFormatOption(option: (() => boolean) | boolean) { @@ -160,8 +158,8 @@ export class Address { export class AddressFormatter { constructor(private options?: Partial) {} // Save and redirect to Address class - public parse(source: string) { - return Address.parse(source, this.options); + public parse(source: string, options?: Partial) { + return Address.parse(source, { ...this.options, ...options }); } public fromPubkey = Address.fromPubkey; diff --git a/packages/@core-js/src/formatters/DNS.ts b/packages/@core-js/src/formatters/DNS.ts new file mode 100644 index 000000000..55915dcb5 --- /dev/null +++ b/packages/@core-js/src/formatters/DNS.ts @@ -0,0 +1,31 @@ +// list of know for client TLDs for TON DNS +export enum KnownTLDs { + TON = 'ton', + TELEGRAM = 't.me', +} + +export class DNS { + // get known TLD from domain. Uses for TLD-specific features like Telegram buttons in UI + static getTLD(domain: string) { + if (!DNS.isValid(domain)) { + return null; + } + + if (domain.endsWith(KnownTLDs.TON)) { + return KnownTLDs.TON; + } + + if (domain.endsWith(KnownTLDs.TELEGRAM)) { + return KnownTLDs.TELEGRAM; + } + + return null; + } + + static isValid(domain?: string) { + if (!domain) { + return false; + } + return domain.includes('.'); + } +} diff --git a/packages/@core-js/src/index.ts b/packages/@core-js/src/index.ts index de2e631e9..2800fec6e 100644 --- a/packages/@core-js/src/index.ts +++ b/packages/@core-js/src/index.ts @@ -1,5 +1,6 @@ export { TonAPI, useTonAPI, TonAPIProvider } from './TonAPI'; export * from './formatters/Address'; +export * from './formatters/DNS'; export * from './utils/AmountFormatter/FiatCurrencyConfig'; export * from './utils/AmountFormatter'; diff --git a/packages/@core-js/src/models/ActivityModel/ActivityModel.ts b/packages/@core-js/src/models/ActivityModel/ActivityModel.ts index 5891692a7..9d8010834 100644 --- a/packages/@core-js/src/models/ActivityModel/ActivityModel.ts +++ b/packages/@core-js/src/models/ActivityModel/ActivityModel.ts @@ -93,6 +93,7 @@ export class ActivityModel { payload, amount, source, + initialActionType: ActionType[event.actions[event.actions.length - 1].type], event, type, }; diff --git a/packages/@core-js/src/models/ActivityModel/ActivityModelTypes.ts b/packages/@core-js/src/models/ActivityModel/ActivityModelTypes.ts index 88ce3070b..b135f3fb8 100644 --- a/packages/@core-js/src/models/ActivityModel/ActivityModelTypes.ts +++ b/packages/@core-js/src/models/ActivityModel/ActivityModelTypes.ts @@ -129,6 +129,7 @@ export interface ActionItem { isLast?: boolean; event: ActionEvent; source: ActionSource; + initialActionType: ActionType; amount?: ActionAmount | null; status: ActionStatusEnum; destination: ActionDestination; diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index 92d902e53..927cbfc7e 100644 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -123,8 +123,8 @@ android { applicationId "com.ton_keeper" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 378 - versionName "3.4" + versionCode 382 + versionName "3.4.1" missingDimensionStrategy 'react-native-camera', 'general' } diff --git a/packages/mobile/index.js b/packages/mobile/index.js index 9f66fa191..b5bb1a383 100644 --- a/packages/mobile/index.js +++ b/packages/mobile/index.js @@ -35,11 +35,7 @@ async function handleDappMessage(remoteMessage) { if (remoteMessage.notification?.body) { return null; } - if ( - !['bridge_dapp_notification', 'console_dapp_notification'].includes( - remoteMessage.data?.type, - ) - ) { + if (!['console_dapp_notification'].includes(remoteMessage.data?.type)) { return null; } diff --git a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj index 34ce837bc..d6f9a94c5 100644 --- a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj @@ -1234,7 +1234,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ton_keeper/ton_keeper.entitlements; - CURRENT_PROJECT_VERSION = 378; + CURRENT_PROJECT_VERSION = 382; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = CT523DK2KC; ENABLE_BITCODE = NO; @@ -1244,7 +1244,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.4; + MARKETING_VERSION = 3.4.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1269,7 +1269,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ton_keeper/ton_keeper.entitlements; - CURRENT_PROJECT_VERSION = 378; + CURRENT_PROJECT_VERSION = 382; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = CT523DK2KC; INFOPLIST_FILE = ton_keeper/SupportingFiles/Info.plist; @@ -1278,7 +1278,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.4; + MARKETING_VERSION = 3.4.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/packages/mobile/src/blockchain/wallet.ts b/packages/mobile/src/blockchain/wallet.ts index f4852d77a..e3d747430 100644 --- a/packages/mobile/src/blockchain/wallet.ts +++ b/packages/mobile/src/blockchain/wallet.ts @@ -633,12 +633,8 @@ export class TonWallet { if (new BigNumber(balances[0]).minus(amount).isLessThan(-balances[1])) { throw new Error(t('send_insufficient_funds')); } - } else { - if ( - new BigNumber(amountNano) - .plus(sendMode === 128 ? 0 : feeNano) - .isGreaterThan(fromInfo.balance) - ) { + } else if (sendMode !== 128) { + if (new BigNumber(amountNano).plus(feeNano).isGreaterThan(fromInfo.balance)) { throw new Error(t('send_insufficient_funds')); } } diff --git a/packages/mobile/src/core/AddressUpdateInfo/AddressUpdateInfo.tsx b/packages/mobile/src/core/AddressUpdateInfo/AddressUpdateInfo.tsx new file mode 100644 index 000000000..3d442e878 --- /dev/null +++ b/packages/mobile/src/core/AddressUpdateInfo/AddressUpdateInfo.tsx @@ -0,0 +1,150 @@ +import { copyText } from '$hooks/useCopyText'; +import { Spacer } from '$uikit'; +import { Address } from '@tonkeeper/core'; +import { t } from '@tonkeeper/shared/i18n'; +import { tk } from '@tonkeeper/shared/tonkeeper'; +import { Pressable, Screen, Steezy, Text, View } from '@tonkeeper/uikit'; +import { DarkTheme } from '@tonkeeper/uikit/src/styles/themes/dark'; +import React, { FC } from 'react'; +import { Platform } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +const fontFamily = Platform.select({ + ios: 'SFMono-Medium', + android: 'RobotoMono-Medium', +}); + +const splitAddress = (address: string) => { + return { + start: address.substring(0, 2), + middle: address.substring(2, address.length - 4), + end: address.substring(address.length - 4, address.length), + }; +}; + +export const AddressUpdateInfo: FC = () => { + const oldAddress = Address.parse(tk.wallet.address.ton.raw).toFriendly({ + bounceable: true, + }); + const newAddress = Address.parse(tk.wallet.address.ton.raw).toFriendly({ + bounceable: false, + }); + const oldStyle = splitAddress(oldAddress); + const newStyle = splitAddress(newAddress); + + return ( + + + + + + {t('address_update.post_top')} + + + + {t('address_update.your_wallet')} + + copyText(oldAddress)} + > + + {t('address_update.old_style')} + + + + {oldStyle.start} + + {oldStyle.middle} + + {oldStyle.end} + + + + + copyText(newAddress)} + > + + {t('address_update.new_style')} + + + + {newStyle.start} + + {newStyle.middle} + + {newStyle.end} + + + + + + {t('address_update.why_change')} + + + {t('address_update.post_rest')} + + + + + {'1. '} + + + + {t('address_update.first_option')} + + + + + + {'2. '} + + + + {t('address_update.second_option')} + + + + {t('address_update.post_dates')} + + + + + + ); +}; + +const styles = Steezy.create(({ colors, corners }) => ({ + container: { + flex: 1, + }, + content: { + paddingTop: 12, + paddingBottom: 16, + paddingHorizontal: 16, + }, + labelContainer: { + paddingVertical: 12, + }, + option: { + paddingLeft: 22, + position: 'relative', + }, + optionNum: { + position: 'absolute', + top: 0, + left: 5, + }, + addressContainer: { + paddingHorizontal: 16, + paddingVertical: 12, + backgroundColor: colors.fieldBackground, + borderRadius: corners.medium, + }, +})); diff --git a/packages/mobile/src/core/ApprovalCell/components/ApprovalCell.tsx b/packages/mobile/src/core/ApprovalCell/components/ApprovalCell.tsx index e68d2a8b2..585578ac3 100644 --- a/packages/mobile/src/core/ApprovalCell/components/ApprovalCell.tsx +++ b/packages/mobile/src/core/ApprovalCell/components/ApprovalCell.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useMemo } from 'react'; import { Steezy } from '$styles'; -import { Icon, Spacer, SText, View, List } from '$uikit'; +import { Icon, SText, View } from '@tonkeeper/uikit'; +import { List } from '$uikit'; import { openManageTokens } from '$navigation'; import { t } from '@tonkeeper/shared/i18n'; import { useApprovedNfts } from '$hooks/useApprovedNfts'; @@ -77,26 +78,23 @@ const ApprovalCellComponent: React.FC = ({ withoutSpacer, sty } return ( - - {!withoutSpacer && } - - - - - } - title={ - - {text} - - } - chevron - /> - - + + + + + } + title={ + + {text} + + } + chevron + /> + ); }; @@ -105,7 +103,7 @@ export const ApprovalCell = React.memo(ApprovalCellComponent); const styles = Steezy.create(({ colors }) => ({ container: { backgroundColor: colors.backgroundContentTint, - marginBottom: 4, + marginBottom: 16, }, title: { marginRight: 40, diff --git a/packages/mobile/src/core/ChooseCountry/ChooseCountry.tsx b/packages/mobile/src/core/ChooseCountry/ChooseCountry.tsx index 675bd2c35..d8c918e2c 100644 --- a/packages/mobile/src/core/ChooseCountry/ChooseCountry.tsx +++ b/packages/mobile/src/core/ChooseCountry/ChooseCountry.tsx @@ -1,12 +1,13 @@ import React, { useCallback, useMemo, useRef } from 'react'; -import { List, NavBar, Screen, View } from '$uikit'; +import { List, Screen, View } from '$uikit'; import { getCountries } from '$utils/countries/getCountries'; -import { Steezy } from '$styles'; import { ListSeparator } from '$uikit/List/ListSeparator'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useMethodsToBuyStore } from '$store/zustand/methodsToBuy/useMethodsToBuyStore'; -import { t } from '@tonkeeper/shared/i18n'; import { goBack } from '$navigation/imperative'; +import { SearchNavBar } from '$core/ChooseCountry/components/SearchNavBar'; +import { Steezy, Text } from '@tonkeeper/uikit'; +import { t } from '@tonkeeper/shared/i18n'; const CELL_SIZE = 56; @@ -18,8 +19,12 @@ const ListSeparatorItem = () => ( const RenderItem = ({ item, + isFirst, + isLast, }: { - item: { code: string; name: string; isFirst: boolean; isLast: boolean }; + item: { code: string; name: string }; + isFirst: boolean; + isLast: boolean; }) => { const setSelectedCountry = useMethodsToBuyStore( (state) => state.actions.setSelectedCountry, @@ -31,8 +36,8 @@ const RenderItem = ({ }, [item.code, setSelectedCountry]); const containerStyle = [ - item.isFirst && styles.firstListItem, - item.isLast && styles.lastListItem, + isFirst && styles.firstListItem, + isLast && styles.lastListItem, styles.containerListItem, ]; @@ -57,13 +62,36 @@ export const ChooseCountry: React.FC = () => { () => countriesList.findIndex((country) => country.code === selectedCountry), [selectedCountry], ); + const [searchValue, setSearchValue] = React.useState(''); + + const filteredListBySearch = useMemo(() => { + if (searchValue) { + return countriesList.filter((country) => { + const regex = new RegExp(`\\b${searchValue}`, 'gi'); + return country.name.match(regex); + }); + } + return countriesList; + }, [searchValue]); + + const searchNavBar = useCallback( + (scrollY) => ( + + ), + [searchValue, setSearchValue], + ); return ( - - {t('choose_country.title')} - + + + {t('choose_country.empty_placeholder')} + + + } initialScrollIndex={selectedCountryIndex} drawDistance={750} ItemSeparatorComponent={ListSeparatorItem} @@ -71,14 +99,25 @@ export const ChooseCountry: React.FC = () => { ref={listRef} contentContainerStyle={{ paddingBottom: bottomInset + 16 }} estimatedItemSize={CELL_SIZE} - renderItem={({ item }) => } - data={countriesList} + renderItem={({ item, index }) => ( + + )} + data={filteredListBySearch} /> ); }; const styles = Steezy.create(({ corners, colors }) => ({ + emptyPlaceholder: { + marginHorizontal: 32, + alignItems: 'center', + marginTop: 56, + }, firstListItem: { borderTopLeftRadius: corners.medium, borderTopRightRadius: corners.medium, diff --git a/packages/mobile/src/core/ChooseCountry/components/SearchNavBar.tsx b/packages/mobile/src/core/ChooseCountry/components/SearchNavBar.tsx new file mode 100644 index 000000000..7e508ab05 --- /dev/null +++ b/packages/mobile/src/core/ChooseCountry/components/SearchNavBar.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { t } from '@tonkeeper/shared/i18n'; +import { SearchInput, Steezy, Text, TouchableOpacity } from '@tonkeeper/uikit'; +import Animated, { SharedValue, useAnimatedStyle } from 'react-native-reanimated'; +import { useTheme } from '$hooks/useTheme'; +import { goBack } from '$navigation/imperative'; + +export interface SearchNavBarProps { + value: string; + onChangeText: (value: string) => void; + scrollY: SharedValue | undefined; +} + +export const SearchNavBar: React.FC = (props) => { + const theme = useTheme(); + const borderStyle = useAnimatedStyle(() => { + return { + borderBottomColor: + props.scrollY && props.scrollY.value > 0 ? theme.colors.border : 'transparent', + }; + }); + + return ( + + + + + + {t('choose_country.cancel')} + + + + + ); +}; + +const styles = Steezy.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + position: 'relative', + top: 0, + bottom: 0, + left: 0, + right: 0, + marginBottom: 16, + marginHorizontal: 16, + marginTop: 16, + }, + borderContainer: { + zIndex: 2, + borderBottomWidth: 0.5, + borderBottomColor: 'transparent', + }, +}); diff --git a/packages/mobile/src/core/HideableAmount/HideableImage.tsx b/packages/mobile/src/core/HideableAmount/HideableImage.tsx index b952c503c..b6653eb92 100644 --- a/packages/mobile/src/core/HideableAmount/HideableImage.tsx +++ b/packages/mobile/src/core/HideableAmount/HideableImage.tsx @@ -1,9 +1,9 @@ -import { StyleProp, ViewStyle, StyleSheet, ImageStyle, Platform } from 'react-native'; +import { ViewStyle, StyleSheet, ImageStyle, Platform } from 'react-native'; import React, { memo, ReactNode } from 'react'; import { useHideableAmount } from '$core/HideableAmount/HideableAmountProvider'; import { useAnimatedStyle } from 'react-native-reanimated'; import { DominantColorBackground } from '$uikit/DominantColorBackground/DominantColorBackground'; -import { View } from '@tonkeeper/uikit'; +import { StyleProp, View } from '@tonkeeper/uikit'; import Animated from 'react-native-reanimated'; import { Steezy } from '$styles'; import { BlurView } from 'expo-blur'; @@ -33,20 +33,15 @@ const HideableImageComponent: React.FC = ({ return ( - - {Platform.OS === 'ios' ? ( - - ) : ( - - )} - + + + {Platform.OS === 'ios' ? ( + + ) : ( + + )} + + {image ?? ( ({ blur: { zIndex: 3, overflow: 'hidden', + backgroundColor: 'transparent', }, image: { zIndex: 2, diff --git a/packages/mobile/src/core/ManageTokens/ManageTokens.tsx b/packages/mobile/src/core/ManageTokens/ManageTokens.tsx index e91cd4b78..c9c66bf79 100644 --- a/packages/mobile/src/core/ManageTokens/ManageTokens.tsx +++ b/packages/mobile/src/core/ManageTokens/ManageTokens.tsx @@ -217,7 +217,7 @@ export const ManageTokens: FC = () => { items={[ { label: t('wallet.tonkens_tab_lable'), value: 'tokens' }, { - label: t('wallet.collectibles_tab_lable'), + label: t('wallet.nft_tab_lable'), value: 'collectibles', withDot: withCollectibleDot, }, diff --git a/packages/mobile/src/core/ModalContainer/AddEditFavoriteAddress/AddEditFavoriteAddress.tsx b/packages/mobile/src/core/ModalContainer/AddEditFavoriteAddress/AddEditFavoriteAddress.tsx index 574ad62ac..5e2a5c89d 100644 --- a/packages/mobile/src/core/ModalContainer/AddEditFavoriteAddress/AddEditFavoriteAddress.tsx +++ b/packages/mobile/src/core/ModalContainer/AddEditFavoriteAddress/AddEditFavoriteAddress.tsx @@ -85,7 +85,7 @@ const AddEditFavoriteAddressComponent: FC = (props) return ( - + ((props) => { const { action, skipHeader, totalFee } = props; const amount = Ton.formatAmount(action.amount); - const address = Address.parse(action.recipient.address).toAll(); + const address = Address.parse(action.recipient.address, { + bounceable: !getFlag('address_style_nobounce'), + }).toAll(); return ( <> @@ -156,13 +160,15 @@ const NftItemTransferAction = React.memo((props) => const { action, totalFee } = props; const item = useDownloadNFT(action.nft); const address = action.recipient - ? Address.parse(action.recipient.address).toAll() + ? Address.parse(action.recipient.address, { + bounceable: !getFlag('address_style_nobounce'), + }).toAll() : { short: '', friendly: '', }; - const isTG = (item.data?.dns || item.data?.name)?.endsWith('.t.me'); + const isTG = DNS.getTLD(item.data?.dns || item.data?.name) === KnownTLDs.TELEGRAM; const caption = React.useMemo(() => { let text = '...'; diff --git a/packages/mobile/src/core/ModalContainer/NFTOperations/ReplaceDomainAddressModal.tsx b/packages/mobile/src/core/ModalContainer/NFTOperations/ReplaceDomainAddressModal.tsx index 39cd2a5ab..19c862546 100644 --- a/packages/mobile/src/core/ModalContainer/NFTOperations/ReplaceDomainAddressModal.tsx +++ b/packages/mobile/src/core/ModalContainer/NFTOperations/ReplaceDomainAddressModal.tsx @@ -44,6 +44,7 @@ export const ReplaceDomainAddressModal: React.FC onChangeText={setAddress} placeholder={t('dns_wallet_address')} value={address} + component={Modal.Input} /> @@ -67,4 +68,4 @@ export function openReplaceDomainAddress(params: { params, path: 'LINKING_REPLACE', }); -} \ No newline at end of file +} diff --git a/packages/mobile/src/core/NFT/Details/Details.tsx b/packages/mobile/src/core/NFT/Details/Details.tsx index 2cc685a97..66393db0b 100644 --- a/packages/mobile/src/core/NFT/Details/Details.tsx +++ b/packages/mobile/src/core/NFT/Details/Details.tsx @@ -10,6 +10,7 @@ import { openDAppBrowser } from '$navigation'; import { Toast } from '$store'; import { format } from 'date-fns'; import { Address } from '@tonkeeper/core'; +import { getFlag } from '$utils/flags'; export const Details: React.FC = ({ tokenId, @@ -19,8 +20,6 @@ export const Details: React.FC = ({ ownerAddress, expiringAt, }) => { - - const handleOpenExplorer = useCallback(() => { openDAppBrowser(getServerConfig('NFTOnExplorerUrl').replace('%s', contractAddress)); }, [contractAddress]); @@ -33,13 +32,17 @@ export const Details: React.FC = ({ [t], ); + const parsedOwnerAddress = Address.parse(ownerAddress, { + bounceable: !getFlag('address_style_nobounce'), + }); + const items = useMemo(() => { let result: { label: string; value: string; copyableValue?: string }[] = []; result.push({ label: t('nft_owner_address'), - value: ownerAddress ? Address.parse(ownerAddress).toShort() : '...', - copyableValue: ownerAddress, + value: ownerAddress ? parsedOwnerAddress.toShort() : '...', + copyableValue: parsedOwnerAddress.toFriendly(), }); if (expiringAt && expiringAt > 0) { @@ -76,9 +79,8 @@ export const Details: React.FC = ({ }); } - return result; - }, [t, tokenId, chain, contractAddress, standard, ownerAddress, expiringAt]); + }, [tokenId, chain, contractAddress, standard, ownerAddress, expiringAt]); return ( diff --git a/packages/mobile/src/core/NFT/LinkingDomainButton.tsx b/packages/mobile/src/core/NFT/LinkingDomainButton.tsx index 07e4681f0..2dc78587c 100644 --- a/packages/mobile/src/core/NFT/LinkingDomainButton.tsx +++ b/packages/mobile/src/core/NFT/LinkingDomainButton.tsx @@ -20,6 +20,7 @@ import { openInsufficientFundsModal, } from '$core/ModalContainer/InsufficientFunds/InsufficientFunds'; import { Address } from '@tonkeeper/core'; +import { useFlags } from '$utils/flags'; const TonWeb = require('tonweb'); // Fix ts types; @@ -70,6 +71,8 @@ interface LinkingDomainButtonProps { } export const LinkingDomainButton = React.memo((props) => { + const flags = useFlags(['address_style_nobounce']); + const version = useSelector(walletVersionSelector); const allAddesses = useAllAddresses(); const dispatch = useDispatch(); @@ -137,7 +140,7 @@ export const LinkingDomainButton = React.memo((props) if (Boolean(record.walletAddress) && Boolean(record.ownerAddress)) { if (Object.values(allAddesses).length > 0) { const linked = Object.values(allAddesses).find((address) => { - return address === record.walletAddress; + return Address.compare(address, record.walletAddress); }); return !linked; @@ -149,7 +152,9 @@ export const LinkingDomainButton = React.memo((props) const handlePressButton = React.useCallback(async () => { Toast.loading(); - const currentWalletAddress = allAddesses[version]; + const currentWalletAddress = Address.parse(allAddesses[version], { + bounceable: !flags.address_style_nobounce, + }).toFriendly(); const linkingActions = new LinkingDomainActions( props.domainAddress, isLinked ? undefined : currentWalletAddress, @@ -199,12 +204,14 @@ export const LinkingDomainButton = React.memo((props) const buttonTitle = React.useMemo(() => { if (record.walletAddress) { - const address = Address.toShort(record.walletAddress!); + const address = Address.parse(record.walletAddress!, { + bounceable: !flags.address_style_nobounce, + }).toShort(); return ' ' + t('nft_unlink_domain_button', { address }); } return t(props.isTGUsername ? 'nft_link_username_button' : 'nft_link_domain_button'); - }, [record.walletAddress, props.isTGUsername]); + }, [record.walletAddress, props.isTGUsername, flags.address_style_nobounce]); return ( diff --git a/packages/mobile/src/core/NFT/NFT.tsx b/packages/mobile/src/core/NFT/NFT.tsx index ea414d7ec..29e30eef5 100644 --- a/packages/mobile/src/core/NFT/NFT.tsx +++ b/packages/mobile/src/core/NFT/NFT.tsx @@ -7,10 +7,10 @@ import Animated, { } from 'react-native-reanimated'; import { ImageWithTitle } from '$core/NFT/ImageWithTitle/ImageWithTitle'; import { - ONE_YEAR_MILISEC, checkIsTelegramNumbersNFT, checkIsTonDiamondsNFT, ns, + ONE_YEAR_MILISEC, } from '$utils'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { t } from '@tonkeeper/shared/i18n'; @@ -18,15 +18,15 @@ import { Properties } from '$core/NFT/Properties/Properties'; import { Details } from '$core/NFT/Details/Details'; import { NFTProps } from '$core/NFT/NFT.interface'; import { useNFT } from '$hooks/useNFT'; -import { Platform, Share, View, TouchableOpacity } from 'react-native'; +import { Platform, Share, TouchableOpacity, View } from 'react-native'; import { TonDiamondFeature } from './TonDiamondFeature/TonDiamondFeature'; import { useDispatch, useSelector } from 'react-redux'; import { walletAddressSelector } from '$store/wallet'; import { NFTModel, TonDiamondMetadata } from '$store/models'; -import { useFlags } from '$utils/flags'; +import { getFlag, useFlags } from '$utils/flags'; import { LinkingDomainButton } from './LinkingDomainButton'; import { nftsActions } from '$store/nfts'; -import { SheetActions, navigation, useNavigation } from '@tonkeeper/router'; +import { navigation, useNavigation } from '@tonkeeper/router'; import { openDAppBrowser } from '$navigation'; import { RenewDomainButton, RenewDomainButtonRef } from './RenewDomainButton'; import { Tonapi } from '$libs/Tonapi'; @@ -34,7 +34,7 @@ import { Toast } from '$store'; import { useExpiringDomains } from '$store/zustand/domains/useExpiringDomains'; import { usePrivacyStore } from '$store/zustand/privacy/usePrivacyStore'; import { ProgrammableButtons } from '$core/NFT/ProgrammableButtons/ProgrammableButtons'; -import { Address } from '@tonkeeper/core'; +import { Address, DNS, KnownTLDs } from '@tonkeeper/core'; import { NftItem } from '@tonkeeper/core/src/TonAPI'; import { tk } from '@tonkeeper/shared/tonkeeper'; import { CryptoCurrencies } from '$shared/constants'; @@ -66,7 +66,7 @@ export const NFT: React.FC = ({ oldNftItem, route }) => { [nft], ); - const isTG = (nft.dns || nft.name)?.endsWith('.t.me'); + const isTG = DNS.getTLD(nft.dns || nft.name) === KnownTLDs.TELEGRAM; const isDNS = !!nft.dns && !isTG; const isTonDiamondsNft = checkIsTonDiamondsNFT(nft); const isNumbersNft = checkIsTelegramNumbersNFT(nft); @@ -103,9 +103,11 @@ export const NFT: React.FC = ({ oldNftItem, route }) => { const scrollTop = useSharedValue(0); const scrollRef = useRef(null); const { bottom: bottomInset } = useSafeAreaInsets(); - const canTransfer = useMemo( - () => Address.compare(nft.ownerAddress, address.ton), - [nft.ownerAddress, address.ton], + + const isOnSale = useMemo(() => !!nft.sale, [nft.sale]); + const isCurrentAddressOwner = useMemo( + () => !isOnSale && Address.compare(nft.ownerAddress, address.ton), + [isOnSale, nft.ownerAddress, address.ton], ); const scrollHandler = useAnimatedScrollHandler({ @@ -142,8 +144,6 @@ export const NFT: React.FC = ({ oldNftItem, route }) => { }); }, [nft.marketplaceURL, nft.name]); - const isOnSale = useMemo(() => !!nft.sale, [nft.sale]); - const lottieUri = isTonDiamondsNft ? nft.metadata?.lottie : undefined; const videoUri = isTonDiamondsNft ? nft.metadata?.animation_url : undefined; @@ -225,7 +225,7 @@ export const NFT: React.FC = ({ oldNftItem, route }) => { +