Skip to content

Commit

Permalink
Merge pull request #938 from tonwhales/release/v2.3.3
Browse files Browse the repository at this point in the history
Release/v2.3.3
  • Loading branch information
vzhovnitsky authored Jun 14, 2024
2 parents ce8ba1e + ba11bd1 commit 197cecf
Show file tree
Hide file tree
Showing 24 changed files with 479 additions and 258 deletions.
2 changes: 1 addition & 1 deletion VERSION_CODE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
196
197
6 changes: 2 additions & 4 deletions app/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ import { useBlocksWatcher } from './engine/accountWatcher';
import { HintsPrefetcher } from './components/HintsPrefetcher';
import { useTonconnectWatcher } from './engine/tonconnectWatcher';
import { useHoldersWatcher } from './engine/holdersWatcher';
import { usePendingWatcher } from './engine/hooks';
import { registerForPushNotificationsAsync, registerPushToken } from './utils/registerPushNotifications';
import * as Notifications from 'expo-notifications';
import { PermissionStatus } from 'expo-modules-core';
Expand Down Expand Up @@ -94,6 +93,7 @@ import { ContactNewFragment } from './fragments/contacts/ContactNewFragment';
import { SearchEngineFragment } from './fragments/SearchEngineFragment';
import { ProductsListFragment } from './fragments/wallet/ProductsListFragment';
import { SortedHintsWatcher } from './components/SortedHintsWatcher';
import { PendingTxsWatcher } from './components/PendingTxsWatcher';

const Stack = createNativeStackNavigator();
Stack.Navigator.displayName = 'MainStack';
Expand Down Expand Up @@ -415,9 +415,6 @@ export const Navigation = memo(() => {
// Watch for holders updates
useHoldersWatcher();

// clear pending txs on account change
usePendingWatcher();

// Jetton hints watcher
const selected = appState.addresses.find((value, index) => index === appState.selected)?.address.toString({ testOnly: isTestnet });

Expand All @@ -443,6 +440,7 @@ export const Navigation = memo(() => {
</NavigationContainer>
<HintsPrefetcher />
<SortedHintsWatcher owner={selected} />
<PendingTxsWatcher />
<Splash hide={hideSplash} />
</View>
);
Expand Down
8 changes: 8 additions & 0 deletions app/components/PendingTxsWatcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { memo } from "react";
import { usePendingWatcher } from "../engine/hooks";

export const PendingTxsWatcher = memo(() => {
// clear pending txs on account change
usePendingWatcher();
return <></>;
});
10 changes: 6 additions & 4 deletions app/components/SelectableButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { memo } from "react";
import { Pressable, View, Text } from "react-native";
import { Pressable, View, Text, StyleProp, ViewStyle } from "react-native";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import { useTheme } from "../engine/hooks";

Expand All @@ -12,20 +12,22 @@ export const SelectableButton = memo((
selected,
onSelect,
icon,
hideSelection
hideSelection,
style
}: {
title: string,
subtitle: string,
selected?: boolean,
onSelect?: () => void,
icon?: any,
hideSelection?: boolean
hideSelection?: boolean,
style?: StyleProp<ViewStyle>
}
) => {
const theme = useTheme();

return (
<Animated.View entering={FadeIn} exiting={FadeOut}>
<Animated.View style={style} entering={FadeIn} exiting={FadeOut}>
<Pressable
style={{
backgroundColor: theme.surfaceOnElevation,
Expand Down
4 changes: 3 additions & 1 deletion app/components/browser/BrowserBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export const BrowserBanner = memo(({
);

navigation.navigateDAppWebView({
lockNativeBack: true,
safeMode: true,
url: banner.product_url,
title: banner.title ?? undefined,
header: { titleComponent: titleComponent },
Expand All @@ -92,7 +94,7 @@ export const BrowserBanner = memo(({
share: true,
back: true,
forward: true
}
},
});
}, [banner]);

Expand Down
2 changes: 2 additions & 0 deletions app/components/browser/BrowserListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export const BrowserListItem = memo(({
);

navigation.navigateDAppWebView({
lockNativeBack: true,
safeMode: true,
url: item.product_url,
title: item.title ?? undefined,
header: { titleComponent },
Expand Down
19 changes: 2 additions & 17 deletions app/engine/hooks/jettons/useJettonWalletAddress.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import { useQuery } from '@tanstack/react-query';
import { Queries } from '../../queries';
import { Address, TonClient4 } from '@ton/ton';
import { tryGetJettonWallet } from '../../metadata/introspections/tryGetJettonWallet';
import { getLastBlock } from '../../accountWatcher';
import { useClient4 } from '../network/useClient4';
import { useNetwork } from '../network/useNetwork';

export function jettonWalletAddressQueryFn(client: TonClient4, master: string, wallet: string, isTestnet: boolean) {
return async () => {
let result = await tryGetJettonWallet(client, await getLastBlock(), { address: Address.parse(wallet), master: Address.parse(master) });
if (!result) {
return null;
}

return result.toString({ testOnly: isTestnet });
}
}
import { jettonWalletAddressQueryFn } from './usePrefetchHints';

export function useJettonWalletAddress(master?: string | null, wallet?: string | null, suspense: boolean = false) {
let isTestnet = useNetwork().isTestnet;
let client = useClient4(isTestnet);

return useQuery({
queryKey: Queries.Jettons().Address(wallet!).Wallet(master!),
queryFn: jettonWalletAddressQueryFn(client, master!, wallet!, isTestnet),
queryFn: jettonWalletAddressQueryFn(master!, wallet!, isTestnet),
enabled: !!master && !!wallet,
suspense
})
Expand Down
55 changes: 53 additions & 2 deletions app/engine/hooks/jettons/usePrefetchHints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useNetwork } from '../network/useNetwork';
import { Queries } from '../../queries';
import { fetchMetadata } from '../../metadata/fetchMetadata';
import { getLastBlock } from '../../accountWatcher';
import { useClient4 } from '../network/useClient4';
import { JettonMasterState, fetchJettonMasterContent } from '../../metadata/fetchJettonMasterContent';
import { Address } from '@ton/core';
import { StoredContractMetadata, StoredJettonWallet } from '../../metadata/StoredMetadata';
Expand All @@ -17,6 +16,7 @@ import { create, keyResolver, windowedFiniteBatchScheduler } from "@yornaath/bat
import { clients } from '../../clients';
import { AsyncLock } from 'teslabot';
import memoize from '../../../utils/memoize';
import { tryGetJettonWallet } from '../../metadata/introspections/tryGetJettonWallet';

let jettonFetchersLock = new AsyncLock();

Expand Down Expand Up @@ -135,6 +135,44 @@ const walletBatcher = memoize((client: TonClient4, isTestnet: boolean) => {
})
});

const walletAddressBatcher = memoize((client: TonClient4, isTestnet: boolean, owner: string) => {
return create({
fetcher: async (masters: string[]) => {
return await jettonFetchersLock.inLock(async () => {
let result: { wallet: string, master: string }[] = [];
let lastBlock = await getLastBlock();
log(`[wallet-address] 🟡 batch ${masters.length}`);
let measurement = performance.now();
await Promise.all(masters.map(async (master) => {
try {
let wallet = await tryGetJettonWallet(
client,
lastBlock,
{ address: Address.parse(owner), master: Address.parse(master) }
);
if (!wallet) {
return;
}

result.push({
wallet: wallet.toString({ testOnly: isTestnet }),
master: master
});
} catch (error) {
console.warn(`[jetton-wallet-address] 🔴 ${owner}`, error);
}
}));
log(`[wallet-address] 🟢 in ${(performance.now() - measurement).toFixed(1)}`);
return result;
});
},
resolver: (items: { wallet: string, master: string }[], query) => {
return items.find(i => i.master === query)?.wallet ?? null;
},
scheduler: windowedFiniteBatchScheduler({ windowMs: 1000, maxBatchSize: 10 })
});
});

export function contractMetadataQueryFn(isTestnet: boolean, addressString: string) {
return async (): Promise<StoredContractMetadata> => {
return metadataBatcher(clients.ton[isTestnet ? 'testnet' : 'mainnet'], isTestnet).fetch(addressString);
Expand All @@ -154,6 +192,12 @@ export function jettonWalletQueryFn(wallet: string, isTestnet: boolean) {
}
}

export function jettonWalletAddressQueryFn(master: string, owner: string, isTestnet: boolean) {
return async (): Promise<string | null> => {
return walletAddressBatcher(clients.ton[isTestnet ? 'testnet' : 'mainnet'], isTestnet, owner).fetch(master);
}
}

const currentJettonsVersion = 3;
const jettonsVersionKey = 'jettons-version';

Expand All @@ -174,7 +218,6 @@ function invalidateJettonsDataIfVersionChanged(queryClient: QueryClient) {
export function usePrefetchHints(queryClient: QueryClient, address?: string) {
const hints = useHints(address);
const { isTestnet } = useNetwork();
const client = useClient4(isTestnet);

useEffect(() => {
if (!address) {
Expand Down Expand Up @@ -217,6 +260,10 @@ export function usePrefetchHints(queryClient: QueryClient, address?: string) {
queryKey: Queries.Jettons().MasterContent(masterAddress),
queryFn: jettonMasterContentQueryFn(masterAddress, isTestnet),
});
await queryClient.prefetchQuery({
queryKey: Queries.Jettons().Address(address).Wallet(masterAddress),
queryFn: jettonWalletAddressQueryFn(masterAddress, address, isTestnet),
});
}

masterContent = queryClient.getQueryData<JettonMasterState>(Queries.Jettons().MasterContent(hint));
Expand All @@ -226,6 +273,10 @@ export function usePrefetchHints(queryClient: QueryClient, address?: string) {
queryKey: Queries.Jettons().MasterContent(hint),
queryFn: jettonMasterContentQueryFn(hint, isTestnet),
});
await queryClient.prefetchQuery({
queryKey: Queries.Jettons().Address(address).Wallet(hint),
queryFn: jettonWalletAddressQueryFn(hint, address, isTestnet),
});
}
}));
})().catch((e) => {
Expand Down
3 changes: 2 additions & 1 deletion app/engine/hooks/transactions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export { usePendingTransactions } from './usePendingTransactions';
export { usePendingWatcher } from './usePendingWatcher';
export { useRegisterPending } from './useRegisterPending';
export { useRawAccountTransactions } from './useRawAccountTransactions';
export { usePeparedMessages } from './usePeparedMessages';
export { usePeparedMessages } from './usePeparedMessages';
export { usePendingActions } from './usePendingActions';
33 changes: 33 additions & 0 deletions app/engine/hooks/transactions/usePendingActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useCallback, useEffect, useRef } from "react";
import { usePendingTransactions } from "..";

export function usePendingActions(address: string, isTestnet: boolean) {
const [pending, setPending] = usePendingTransactions(address, isTestnet);
const setPendingRef = useRef(setPending);

useEffect(() => {
setPendingRef.current = setPending;
}, [setPending]);

const removePending = useCallback((ids: string[]) => {
if (ids.length === 0) {
return;
}
setPendingRef.current((prev) => {
return prev.filter((tx) => !ids.includes(tx.id));
});
}, []);

const markAsTimedOut = useCallback((id: string) => {
setPendingRef.current((prev) => {
return prev.map((tx) => {
if (tx.id === id) {
return { ...tx, status: 'timed-out' };
}
return tx;
});
});
}, []);

return { state: pending, removePending, markAsTimedOut };
}
2 changes: 1 addition & 1 deletion app/engine/state/pending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type PendingTransactionBody =
}
| { type: 'batch' };

export type PendingTransactionStatus = 'pending' | 'sent';
export type PendingTransactionStatus = 'pending' | 'sent' | 'timed-out';

export type PendingTransaction = {
id: string,
Expand Down
29 changes: 17 additions & 12 deletions app/fragments/staking/LiquidStakingFragment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from "react";
import React, { useCallback, useEffect, useMemo } from "react";
import { View, Text, Platform, Image, Pressable, ScrollView } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { PriceComponent } from "../../components/PriceComponent";
Expand All @@ -11,12 +11,12 @@ import { t } from "../../i18n/t";
import { KnownPools, getLiquidStakingAddress } from "../../utils/KnownPools";
import { useFocusEffect, useRoute } from "@react-navigation/native";
import { StakingAnalyticsComponent } from "../../components/staking/StakingAnalyticsComponent";
import { useLiquidStakingMember, useNetwork, usePendingTransactions, useSelectedAccount, useStakingApy, useTheme } from "../../engine/hooks";
import { useLiquidStakingMember, useNetwork, usePendingActions, useSelectedAccount, useStakingApy, useTheme } from "../../engine/hooks";
import { useLedgerTransport } from "../ledger/components/TransportContext";
import { Address, fromNano, toNano } from "@ton/core";
import { StatusBar, setStatusBarStyle } from "expo-status-bar";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import { PendingTransactionsView } from "../wallet/views/PendingTransactions";
import { PendingTransactionsList } from "../wallet/views/PendingTransactions";
import { useLiquidStaking } from "../../engine/hooks/staking/useLiquidStaking";
import { Typography } from "../../components/styles";
import { BackButton } from "../../components/navigation/BackButton";
Expand All @@ -35,7 +35,6 @@ export const LiquidStakingFragment = fragment(() => {
const selected = useSelectedAccount();
const bottomBarHeight = useBottomTabBarHeight();
const liquidStaking = useLiquidStaking().data;
const [pendingTxs, setPending] = usePendingTransactions(selected?.addressString ?? '', network.isTestnet);
const ledgerContext = useLedgerTransport();
const ledgerAddress = useMemo(() => {
if (!isLedger || !ledgerContext?.addr?.address) return;
Expand All @@ -46,6 +45,7 @@ export const LiquidStakingFragment = fragment(() => {
const memberAddress = isLedger ? ledgerAddress : selected?.address;
const nominator = useLiquidStakingMember(memberAddress)?.data;
const apy = useStakingApy()?.apy;
const { state: pendingTxs, removePending, markAsTimedOut } = usePendingActions(memberAddress!.toString({ testOnly: network.isTestnet }), network.isTestnet);

const poolFee = liquidStaking?.extras.poolFee ? Number(toNano(fromNano(liquidStaking?.extras.poolFee))) / 100 : undefined;
const apyWithFee = useMemo(() => {
Expand Down Expand Up @@ -76,11 +76,16 @@ export const LiquidStakingFragment = fragment(() => {
});
}, [pendingTxs, targetPool]);

const removePending = useCallback((id: string) => {
setPending((prev) => {
return prev.filter((tx) => tx.id !== id);
});
}, [setPending]);
useEffect(() => {
// Remove transactions after 15 seconds of changing status
setTimeout(() => {
const toRemove = pendingPoolTxs
.filter((tx) => tx.status !== 'pending')
.map((tx) => tx.id);

removePending(toRemove);
}, 15 * 1000);
}, [pendingPoolTxs]);

const transferAmount = useMemo(() => {
return (liquidStaking?.extras.minStake ?? 0n)
Expand Down Expand Up @@ -383,10 +388,10 @@ export const LiquidStakingFragment = fragment(() => {
style={{ marginBottom: 16 }}
/>
{!!pendingPoolTxs && pendingPoolTxs.length > 0 && (
<PendingTransactionsView
<PendingTransactionsList
theme={theme}
pending={pendingPoolTxs}
removePending={removePending}
txs={pendingPoolTxs}
timeOut={markAsTimedOut}
style={{ marginBottom: 16 }}
/>
)}
Expand Down
Loading

0 comments on commit 197cecf

Please sign in to comment.