mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 22:53:27 +08:00
feat: add brc-20 send for non-zero-index taproot addresses, closes #3830
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
import {
|
||||
Brc20Token,
|
||||
useBrc20TokensByAddressQuery,
|
||||
useBrc20TokensQuery,
|
||||
} from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
|
||||
import { useCurrentAccountTaprootIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
|
||||
interface Brc20TokensLoaderProps {
|
||||
children(brc20Tokens: Brc20Token[]): JSX.Element;
|
||||
}
|
||||
export function Brc20TokensLoader({ children }: Brc20TokensLoaderProps) {
|
||||
const { address: bitcoinAddressTaproot } = useCurrentAccountTaprootIndexZeroSigner();
|
||||
const { data: brc20Tokens } = useBrc20TokensByAddressQuery(bitcoinAddressTaproot);
|
||||
if (!bitcoinAddressTaproot || !brc20Tokens) return null;
|
||||
const { data: allBrc20TokensResponse } = useBrc20TokensQuery();
|
||||
const brc20Tokens = allBrc20TokensResponse?.pages
|
||||
.flatMap(page => page.brc20Tokens)
|
||||
.filter(token => token.length > 0)
|
||||
.flatMap(token => token);
|
||||
|
||||
if (!brc20Tokens) return null;
|
||||
return children(brc20Tokens);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { AppUseQueryConfig } from '@app/query/query-config';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { createNumArrayOfRange } from '@app/common/utils';
|
||||
import { QueryPrefixes } from '@app/query/query-prefixes';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
|
||||
const addressesSimultaneousFetchLimit = 5;
|
||||
const stopSearchAfterNumberAddressesWithoutBrc20Tokens = 5;
|
||||
|
||||
interface Brc20TokenResponse {
|
||||
available_balance: string;
|
||||
@@ -53,15 +62,81 @@ async function fetchBrc20TokensByAddress(address: string): Promise<Brc20Token[]>
|
||||
});
|
||||
}
|
||||
|
||||
type FetchBrc20TokensByAddressResp = Awaited<ReturnType<typeof fetchBrc20TokensByAddress>>;
|
||||
export function useBrc20TokensQuery() {
|
||||
const network = useCurrentNetwork();
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const currentBitcoinAddress = nativeSegwitSigner.address;
|
||||
const createSigner = useCurrentAccountTaprootSigner();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
export function useBrc20TokensByAddressQuery<T extends unknown = FetchBrc20TokensByAddressResp>(
|
||||
address: string,
|
||||
options?: AppUseQueryConfig<FetchBrc20TokensByAddressResp, T>
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: [QueryPrefixes.Brc20TokenBalance, address],
|
||||
queryFn: () => fetchBrc20TokensByAddress(address),
|
||||
...options,
|
||||
if (!createSigner) throw new Error('No signer');
|
||||
|
||||
const getNextTaprootAddressBatch = useCallback(
|
||||
(fromIndex: number, toIndex: number) => {
|
||||
return createNumArrayOfRange(fromIndex, toIndex - 1).map(num => {
|
||||
const address = createSigner(num).address;
|
||||
return address;
|
||||
});
|
||||
},
|
||||
[createSigner]
|
||||
);
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: [QueryPrefixes.Brc20InfiniteQuery, currentBitcoinAddress, network.id],
|
||||
async queryFn({ pageParam }) {
|
||||
const fromIndex: number = pageParam?.fromIndex ?? 0;
|
||||
let addressesWithoutTokens = pageParam?.addressesWithoutTokens ?? 0;
|
||||
|
||||
const addressesData = getNextTaprootAddressBatch(
|
||||
fromIndex,
|
||||
fromIndex + addressesSimultaneousFetchLimit
|
||||
);
|
||||
const brc20TokensPromises = addressesData.map(address => {
|
||||
return fetchBrc20TokensByAddress(address);
|
||||
});
|
||||
|
||||
const brc20Tokens = await Promise.all(brc20TokensPromises);
|
||||
addressesWithoutTokens += brc20Tokens.filter(tokens => tokens.length === 0).length;
|
||||
|
||||
return {
|
||||
addressesWithoutTokens,
|
||||
brc20Tokens,
|
||||
fromIndex,
|
||||
};
|
||||
},
|
||||
getNextPageParam(prevInscriptionQuery) {
|
||||
const { fromIndex, brc20Tokens, addressesWithoutTokens } = prevInscriptionQuery;
|
||||
|
||||
if (addressesWithoutTokens >= stopSearchAfterNumberAddressesWithoutBrc20Tokens) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
fromIndex: fromIndex + addressesSimultaneousFetchLimit,
|
||||
addressesWithoutTokens,
|
||||
brc20Tokens,
|
||||
};
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 3 * 60 * 1000,
|
||||
});
|
||||
|
||||
// Auto-trigger next request
|
||||
useEffect(() => {
|
||||
void query.fetchNextPage();
|
||||
}, [query, query.data]);
|
||||
useEffect(() => {
|
||||
const brc20AcrossAddressesCount = query.data?.pages.reduce((acc, page) => {
|
||||
return acc + page.brc20Tokens.flatMap(item => item).length;
|
||||
}, 0);
|
||||
|
||||
if (!query.hasNextPage && brc20AcrossAddressesCount && brc20AcrossAddressesCount > 0) {
|
||||
void analytics.identify({
|
||||
brc20_across_addresses_count: brc20AcrossAddressesCount,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [analytics, query.hasNextPage]);
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ export enum QueryPrefixes {
|
||||
|
||||
StampCollection = 'stamp-collection',
|
||||
StampsByAddress = 'stamps-by-address',
|
||||
Brc20InfiniteQuery = 'brc20-infinite-query',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user