Skip to content

Commit

Permalink
feat: tweak bitcoin queries for extension
Browse files Browse the repository at this point in the history
Co-authored-by: kyranjamie <[email protected]>
  • Loading branch information
edgarkhanzadian and kyranjamie committed May 7, 2024
1 parent 7f21492 commit dc92174
Show file tree
Hide file tree
Showing 33 changed files with 1,149 additions and 277 deletions.
4 changes: 2 additions & 2 deletions packages/bitcoin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
"@noble/hashes": "1.3.3",
"@noble/secp256k1": "2.0.0",
"@scure/base": "1.1.3",
"@scure/bip32": "1.3.2",
"@scure/bip32": "1.3.3",
"@scure/bip39": "1.2.1",
"@scure/btc-signer": "1.1.0",
"@scure/btc-signer": "1.2.1",
"@stacks/common": "6.8.1",
"@stacks/transactions": "6.9.0",
"bip32": "4.0.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export interface NetworkConfiguration {
};
}

export const BESTINSLOT_API_BASE_URL_MAINNET = 'https://leatherapi.bestinslot.xyz/v3';
export const BESTINSLOT_API_BASE_URL_TESTNET = 'https://leatherapi_testnet.bestinslot.xyz/v3';

export const HIRO_API_BASE_URL_MAINNET = 'https://api.hiro.so';
export const HIRO_API_BASE_URL_TESTNET = 'https://api.testnet.hiro.so';
export const HIRO_INSCRIPTIONS_API_URL = 'https://api.hiro.so/ordinals/v1/inscriptions';
Expand Down
20 changes: 19 additions & 1 deletion packages/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,35 @@ export * from './src/bitcoin/bitcoin-client';
export * from './src/bitcoin/contract/send-accepted-bitcoin-contract-offer';
export * from './src/bitcoin/fees/fee-estimates.hooks';
export * from './src/bitcoin/fees/fee-estimates.query';
export * from './src/bitcoin/ordinals/brc20/brc-20.utils';
export * from './src/bitcoin/ordinals/brc20/brc20-tokens.hooks';
export * from './src/bitcoin/ordinals/brc20/brc20-tokens.query';
export * from './src/bitcoin/ordinals/brc20/brc20-tokens.utils';
export * from './src/bitcoin/ordinals/inscription-by-id.query';
export * from './src/bitcoin/ordinals/inscription-text-content.query';
export * from './src/bitcoin/ordinals/inscription.hooks';
export * from './src/bitcoin/ordinals/inscription.query';
export * from './src/bitcoin/ordinals/inscriptions-by-param.query';
export * from './src/bitcoin/ordinals/inscriptions.hooks';
export * from './src/bitcoin/ordinals/inscriptions.query';
export * from './src/bitcoin/ordinalsbot-client';
export * from './src/bitcoin/runes/runes-outputs-by-address.query';
export * from './src/bitcoin/runes/runes-ticker-info.query';
export * from './src/bitcoin/runes/runes-wallet-balances.query';
export * from './src/bitcoin/runes/runes.hooks';
export * from './src/bitcoin/stamps/stamps-by-address.hooks';
export * from './src/bitcoin/stamps/stamps-by-address.query';
export * from './src/bitcoin/transaction/transaction.query';
export * from './src/bitcoin/transaction/use-bitcoin-broadcast-transaction';
export * from './src/bitcoin/transaction/use-check-utxos';
export * from './src/common/market-data/market-data.hooks';
export * from './src/common/market-data/market-data.query';
export * from './src/common/market-data/vendors/binance-market-data.query';
export * from './src/common/market-data/vendors/coincap-market-data.query';
export * from './src/common/market-data/vendors/coingecko-market-data.query';
export * from './src/common/remote-config/remote-config.query';
export * from './src/leather-query-provider';
export * from './types/account';
export * from './types/api-types';
export * from './types/contract-types';
export * from './types/inscription';
export * from './types/utxo';
11 changes: 8 additions & 3 deletions packages/query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"@leather-wallet/utils": "workspace:*",
"@noble/hashes": "1.3.3",
"@scure/base": "1.1.3",
"@scure/bip32": "1.3.2",
"@scure/btc-signer": "1.1.0",
"@scure/bip32": "1.3.3",
"@scure/btc-signer": "1.2.1",
"@stacks/common": "6.8.1",
"@stacks/rpc-client": "0.8.18",
"@stacks/stacks-blockchain-api-types": "7.8.2",
Expand All @@ -40,14 +40,19 @@
"@tanstack/react-query-persist-client": "4.35.7",
"axios": "1.6.7",
"bignumber.js": "9.1.2",
"yup": "1.3.3"
"lodash.get": "4.4.2",
"p-queue": "8.0.1",
"url-join": "5.0.0",
"yup": "1.3.3",
"zod": "3.23.6"
},
"devDependencies": {
"@leather-wallet/eslint-config": "workspace:*",
"@leather-wallet/prettier-config": "workspace:*",
"@leather-wallet/tsconfig-config": "workspace:*",
"@tanstack/eslint-plugin-query": "5.20.1",
"@types/jsdom": "21.1.3",
"@types/lodash.get": "4.4.9",
"@types/react": "18.2.64",
"@vitest/coverage-istanbul": "0.34.6",
"eslint": "8.53.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { BitcoinTx } from '@leather-wallet/models';
import { useQueries, useQuery } from '@tanstack/react-query';
import { QueryFunctionContext, useQueries, useQuery } from '@tanstack/react-query';

import { AppUseQueryConfig } from '../../query-config';
import { useBitcoinClient } from '../bitcoin-client';

const staleTime = 10 * 1000;

const queryOptions = { staleTime, refetchInterval: staleTime };
const queryOptions = { staleTime, cacheTime: Infinity, refetchInterval: staleTime };

export function useGetBitcoinTransactionsByAddressQuery<T extends unknown = BitcoinTx[]>(
address: string,
Expand All @@ -17,12 +17,11 @@ export function useGetBitcoinTransactionsByAddressQuery<T extends unknown = Bitc
return useQuery({
enabled: !!address,
queryKey: ['btc-txs-by-address', address],
queryFn: () => client.addressApi.getTransactionsByAddress(address),
queryFn: async ({ signal }) => client.addressApi.getTransactionsByAddress(address, signal),
...queryOptions,
...options,
});
}

export function useGetBitcoinTransactionsByAddressesQuery<T extends unknown = BitcoinTx[]>(
addresses: string[],
options?: AppUseQueryConfig<BitcoinTx[], T>
Expand All @@ -34,7 +33,8 @@ export function useGetBitcoinTransactionsByAddressesQuery<T extends unknown = Bi
return {
enabled: !!address,
queryKey: ['btc-txs-by-address', address],
queryFn: () => client.addressApi.getTransactionsByAddress(address),
queryFn: async ({ signal }: QueryFunctionContext<string[], any>) =>
client.addressApi.getTransactionsByAddress(address, signal),
...queryOptions,
...options,
};
Expand Down
87 changes: 74 additions & 13 deletions packages/query/src/bitcoin/address/utxos-by-address.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useCallback } from 'react';

import type { InscriptionResponseItem } from '../../../types/inscription';
import { UtxoResponseItem, UtxoWithDerivationPath } from '../../../types/utxo';
import { RunesOutputsByAddress } from '../bitcoin-client';
import { useInscriptionsByAddressQuery } from '../ordinals/inscriptions.query';
import { useRunesEnabled, useRunesOutputsByAddress } from '../runes/runes.hooks';
import { useBitcoinPendingTransactionsInputs } from './transactions-by-address.hooks';
import { useGetUtxosByAddressQuery } from './utxos-by-address.query';

Expand All @@ -18,29 +20,42 @@ export function filterUtxosWithInscriptions(
);
}

export function filterUtxosWithRunes(runes: RunesOutputsByAddress[], utxos: UtxoResponseItem[]) {
return utxos.filter(utxo => {
const hasRuneOutput = runes.find(rune => {
return rune.output === `${utxo.txid}:${utxo.vout}`;
});

return !hasRuneOutput;
});
}

const defaultArgs = {
filterInscriptionUtxos: true,
filterPendingTxsUtxos: true,
filterRunesUtxos: true,
};

/**
* Warning: ⚠️ To avoid spending inscriptions, when using UTXOs
* we set `filterInscriptionUtxos` and `filterPendingTxsUtxos` to true
*/
export function useCurrentNativeSegwitUtxos(nativeSegwitAddress: string, args = defaultArgs) {
const { filterInscriptionUtxos, filterPendingTxsUtxos } = args;
const { filterInscriptionUtxos, filterPendingTxsUtxos, filterRunesUtxos } = args;

return useNativeSegwitUtxosByAddress({
address: nativeSegwitAddress,
filterInscriptionUtxos,
filterPendingTxsUtxos,
filterRunesUtxos,
});
}

interface UseFilterUtxosByAddressArgs {
address: string;
filterInscriptionUtxos: boolean;
filterPendingTxsUtxos: boolean;
filterRunesUtxos: boolean;
}

type filterUtxoFunctionType = (utxos: UtxoResponseItem[]) => UtxoResponseItem[];
Expand All @@ -49,11 +64,14 @@ export function useNativeSegwitUtxosByAddress({
address,
filterInscriptionUtxos,
filterPendingTxsUtxos,
filterRunesUtxos,
}: UseFilterUtxosByAddressArgs) {
const filterOutInscriptions = useFilterInscriptionsByAddress(address);
const filterOutPendingTxsUtxos = useFilterPendingUtxosByAddress(address);
const { filterOutInscriptions, isInitialLoadingInscriptions } =
useFilterInscriptionsByAddress(address);
const { filterOutPendingTxsUtxos, isInitialLoading } = useFilterPendingUtxosByAddress(address);
const { filterOutRunesUtxos, isInitialLoadingRunesData } = useFilterRuneUtxosByAddress(address);

return useGetUtxosByAddressQuery(address, {
const utxosQuery = useGetUtxosByAddressQuery(address, {
select(utxos) {
const filters = [];
if (filterPendingTxsUtxos) {
Expand All @@ -64,40 +82,78 @@ export function useNativeSegwitUtxosByAddress({
filters.push(filterOutInscriptions);
}

if (filterRunesUtxos) {
filters.push(filterOutRunesUtxos);
}

return filters.reduce(
(filteredUtxos: UtxoResponseItem[], filterFunc: filterUtxoFunctionType) =>
filterFunc(filteredUtxos),
utxos
);
},
});

return {
...utxosQuery,
isInitialLoading:
utxosQuery.isInitialLoading ||
isInitialLoading ||
isInitialLoadingInscriptions ||
isInitialLoadingRunesData,
};
}

function useFilterInscriptionsByAddress(address: string) {
const {
data: inscriptionsList,
hasNextPage: hasMoreInscriptionsToLoad,
isLoading: isLoadingInscriptions,
isInitialLoading: isInitialLoadingInscriptions,
} = useInscriptionsByAddressQuery(address);

return useCallback(
const filterOutInscriptions = useCallback(
(utxos: UtxoResponseItem[]) => {
// While infinite query checks if has more data to load, or Stamps
// are loading, assume nothing is spendable
if (hasMoreInscriptionsToLoad || isLoadingInscriptions) return [];

const inscriptions = inscriptionsList?.pages.flatMap(page => page.results) ?? [];

return filterUtxosWithInscriptions(inscriptions, utxos);
},
[hasMoreInscriptionsToLoad, inscriptionsList?.pages, isLoadingInscriptions]
[inscriptionsList?.pages]
);

return {
filterOutInscriptions,
isInitialLoadingInscriptions: hasMoreInscriptionsToLoad || isInitialLoadingInscriptions,
};
}

function useFilterRuneUtxosByAddress(address: string) {
// TO-DO what if data is undefined?
const { data = [], isInitialLoading } = useRunesOutputsByAddress(address);
const runesEnabled = useRunesEnabled();

const filterOutRunesUtxos = useCallback(
(utxos: UtxoResponseItem[]) => {
// If Runes are not enabled, return all utxos
if (!runesEnabled) {
return utxos;
}

return filterUtxosWithRunes(data, utxos);
},
[data, runesEnabled]
);

return {
filterOutRunesUtxos,
isInitialLoadingRunesData: isInitialLoading,
};
}

function useFilterPendingUtxosByAddress(address: string) {
const { data: pendingInputs = [] } = useBitcoinPendingTransactionsInputs(address);
const { data: pendingInputs = [], isInitialLoading } =
useBitcoinPendingTransactionsInputs(address);

return useCallback(
const filterOutPendingTxsUtxos = useCallback(
(utxos: UtxoResponseItem[]) => {
return utxos.filter(
utxo =>
Expand All @@ -108,4 +164,9 @@ function useFilterPendingUtxosByAddress(address: string) {
},
[address, pendingInputs]
);

return {
filterOutPendingTxsUtxos,
isInitialLoading,
};
}
5 changes: 3 additions & 2 deletions packages/query/src/bitcoin/address/utxos-by-address.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ export function useGetUtxosByAddressQuery<T extends unknown = UtxoResponseItem[]
options?: AppUseQueryConfig<UtxoResponseItem[], T>
) {
const client = useBitcoinClient();

return useQuery({
enabled: !!address,
queryKey: ['btc-utxos-by-address', address],
queryFn: () => client.addressApi.getUtxosByAddress(address),
queryFn: async ({ signal }) => client.addressApi.getUtxosByAddress(address, signal),
...queryOptions,
...options,
});
}

const stopSearchAfterNumberAddressesWithoutUtxos = 20;
const stopSearchAfterNumberAddressesWithoutUtxos = 5;

/**
* Returns all utxos for the user's current taproot account. The search for
Expand Down
20 changes: 18 additions & 2 deletions packages/query/src/bitcoin/balance/btc-balance.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@ import { createMoney, isUndefined, sumNumbers } from '@leather-wallet/utils';
import BigNumber from 'bignumber.js';

import { useNativeSegwitUtxosByAddress } from '../address/utxos-by-address.hooks';
import { useRunesEnabled } from '../runes/runes.hooks';

export function useGetBitcoinBalanceByAddress(address: string) {
const { data: utxos } = useNativeSegwitUtxosByAddress({
const runesEnabled = useRunesEnabled();

const {
data: utxos,
isInitialLoading,
isLoading,
isFetching,
} = useNativeSegwitUtxosByAddress({
address,
filterInscriptionUtxos: true,
filterPendingTxsUtxos: true,
filterRunesUtxos: runesEnabled,
});

return useMemo(() => {
const balance = useMemo(() => {
if (isUndefined(utxos)) return createMoney(new BigNumber(0), 'BTC');
return createMoney(sumNumbers(utxos.map(utxo => utxo.value)), 'BTC');
}, [utxos]);

return {
balance,
isInitialLoading,
isLoading,
isFetching,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ import { useGetBitcoinBalanceByAddress } from './btc-balance.hooks';

// Balance is derived from a single query in address reuse mode
export function useNativeSegwitBalance(address: string) {
const balance = useGetBitcoinBalanceByAddress(address);
return useMemo(() => createBitcoinCryptoCurrencyAssetTypeWrapper(balance), [balance]);
const { balance, isInitialLoading, isLoading, isFetching } =
useGetBitcoinBalanceByAddress(address);

const wrappedBalance = useMemo(
() => createBitcoinCryptoCurrencyAssetTypeWrapper(balance),
[balance]
);

return {
btcBalance: wrappedBalance,
isInitialLoading,
isLoading,
isFetching,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ export function useCurrentTaprootAccountUninscribedUtxos({
nativeSegwitAddress: string;
}) {
const { data: utxos = [] } = useTaprootAccountUtxosQuery({
taprootKeychain,
taprootKeychain: taprootKeychain,
currentAccountIndex,
});

const query = useGetInscriptionsInfiniteQuery({ taprootKeychain, nativeSegwitAddress });
const query = useGetInscriptionsInfiniteQuery({
taprootKeychain,
nativeSegwitAddress,
});

return useMemo(() => {
const inscriptions = query.data?.pages?.flatMap(page => page.inscriptions) ?? [];
Expand Down
Loading

0 comments on commit dc92174

Please sign in to comment.