Skip to content

Commit

Permalink
feat: refactor ordinals (#268)
Browse files Browse the repository at this point in the history
* feat: refactor ordinals

* fix: getInscriptions method

* fix: dust filter
  • Loading branch information
totraev authored Oct 30, 2024
1 parent 56ebe6f commit 061f71c
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 320 deletions.
1 change: 0 additions & 1 deletion src/app/components/Staking/Staking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export const Staking = () => {
network: btcWalletNetwork,
getNetworkFees,
signPsbt,
getWalletProviderName,
pushTx,
} = useBTCWallet();

Expand Down
78 changes: 43 additions & 35 deletions src/app/context/wallet/BTCWalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { BTCProvider } from "@tomo-inc/tomo-wallet-provider";
import type { networks } from "bitcoinjs-lib";
import {
createContext,
Expand All @@ -21,7 +22,6 @@ import {
InscriptionIdentifier,
Network,
UTXO,
type BTCWalletProvider as IBTCWalletProvider,
} from "@/utils/wallet/btc_wallet_provider";
import { WalletError, WalletErrorType } from "@/utils/wallet/errors";

Expand All @@ -34,7 +34,6 @@ interface BTCWalletContextProps {
connected: boolean;
disconnect: () => void;
open: () => void;
getWalletProviderName: () => Promise<string>;
getAddress: () => Promise<string>;
getPublicKeyHex: () => Promise<string>;
signPsbt: (psbtHex: string) => Promise<string>;
Expand All @@ -56,24 +55,22 @@ const BTCWalletContext = createContext<BTCWalletContextProps>({
address: "",
disconnect: () => {},
open: () => {},
getWalletProviderName: () => Promise.resolve(""),
getAddress: () => Promise.resolve(""),
getPublicKeyHex: () => Promise.resolve(""),
signPsbt: () => Promise.resolve(""),
signPsbts: () => Promise.resolve([]),
getNetwork: () => Promise.resolve({} as Network),
signMessageBIP322: () => Promise.resolve(""),
getBalance: () => Promise.resolve(0),
getNetworkFees: () => Promise.resolve({} as Fees),
pushTx: () => Promise.resolve(""),
getUtxos: () => Promise.resolve([]),
getBTCTipHeight: () => Promise.resolve(0),
getInscriptions: () => Promise.resolve([]),
getAddress: async () => "",
getPublicKeyHex: async () => "",
signPsbt: async () => "",
signPsbts: async () => [],
getNetwork: async () => ({}) as Network,
signMessageBIP322: async () => "",
getBalance: async () => 0,
getNetworkFees: async () => ({}) as Fees,
pushTx: async () => "",
getUtxos: async () => [],
getBTCTipHeight: async () => 0,
getInscriptions: async () => [],
});

export const BTCWalletProvider = ({ children }: PropsWithChildren) => {
const [btcWalletProvider, setBTCWalletProvider] =
useState<IBTCWalletProvider>();
const [btcWalletProvider, setBTCWalletProvider] = useState<BTCProvider>();
const [network, setNetwork] = useState<networks.Network>();
const [publicKeyNoCoord, setPublicKeyNoCoord] = useState("");
const [address, setAddress] = useState("");
Expand All @@ -89,7 +86,7 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => {
}, []);

const connectBTC = useCallback(
async (walletProvider: IBTCWalletProvider) => {
async (walletProvider: BTCProvider) => {
const supportedNetworkMessage =
"Only Native SegWit and Taproot addresses are supported. Please switch the address type in your wallet and try again.";

Expand Down Expand Up @@ -156,22 +153,33 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => {

const btcWalletMethods = useMemo(
() => ({
getWalletProviderName: () => btcWalletProvider!.getWalletProviderName(),
getAddress: () => btcWalletProvider!.getAddress(),
getPublicKeyHex: () => btcWalletProvider!.getPublicKeyHex(),
signPsbt: (psbtHex: string) => btcWalletProvider!.signPsbt(psbtHex),
signPsbts: (psbtsHexes: string[]) =>
btcWalletProvider!.signPsbts(psbtsHexes),
getNetwork: () => btcWalletProvider!.getNetwork(),
signMessageBIP322: (message: string) =>
btcWalletProvider!.signMessageBIP322(message),
getBalance: () => btcWalletProvider!.getBalance(),
getNetworkFees: () => btcWalletProvider!.getNetworkFees(),
pushTx: (txHex: string) => btcWalletProvider!.pushTx(txHex),
getUtxos: (address: string, amount?: number) =>
btcWalletProvider!.getUtxos(address, amount),
getBTCTipHeight: () => btcWalletProvider!.getBTCTipHeight(),
getInscriptions: () => btcWalletProvider!.getInscriptions(),
getAddress: async () => btcWalletProvider?.getAddress() ?? "",
getPublicKeyHex: async () => btcWalletProvider?.getPublicKeyHex() ?? "",
signPsbt: async (psbtHex: string) =>
btcWalletProvider?.signPsbt(psbtHex) ?? "",
signPsbts: async (psbtsHexes: string[]) =>
btcWalletProvider?.signPsbts(psbtsHexes) ?? [],
getNetwork: async () =>
btcWalletProvider?.getNetwork() ?? ({} as Network),
signMessageBIP322: async (message: string) =>
btcWalletProvider?.signMessageBIP322(message) ?? "",
getBalance: async () => btcWalletProvider?.getBalance() ?? 0,
getNetworkFees: async () =>
btcWalletProvider?.getNetworkFees() ?? ({} as Fees),
pushTx: async (txHex: string) => btcWalletProvider?.pushTx(txHex) ?? "",
getUtxos: async (address: string, amount?: number) =>
btcWalletProvider?.getUtxos(address, amount) ?? [],
getBTCTipHeight: async () => btcWalletProvider?.getBTCTipHeight() ?? 0,
getInscriptions: async (): Promise<InscriptionIdentifier[]> =>
btcWalletProvider
?.getInscriptions()
.then((result) =>
result.list.map((ordinal) => ({
txid: ordinal.inscriptionId,
vout: ordinal.outputValue,
})),
)
.catch((e) => []) ?? [],
}),
[btcWalletProvider],
);
Expand Down Expand Up @@ -200,7 +208,7 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => {
useEffect(() => {
if (isConnected && providers.state) {
if (!btcWalletProvider && providers.bitcoinProvider) {
connectBTC(providers.bitcoinProvider as unknown as IBTCWalletProvider);
connectBTC(providers.bitcoinProvider);
}
}
}, [
Expand Down
46 changes: 46 additions & 0 deletions src/app/hooks/api/useOrdinals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { postVerifyUtxoOrdinals } from "@/app/api/postFilterOrdinals";
import { ONE_MINUTE } from "@/app/constants";
import { useBTCWallet } from "@/app/context/wallet/BTCWalletProvider";
import { useAPIQuery } from "@/app/hooks/api/useApi";
import { wait } from "@/utils";
import { filterDust } from "@/utils/wallet";
import {
InscriptionIdentifier,
UTXO,
} from "@/utils/wallet/btc_wallet_provider";

export const ORDINAL_KEY = "ORDINALS";
export const WALLET_FETCH_INSRIPTIONS_TIMEOUT = 3_000;

export function useOrdinals(
utxos: UTXO[],
{ enabled = true }: { enabled?: boolean } = {},
) {
const { getInscriptions, address } = useBTCWallet();

const fetchOrdinals = async (): Promise<InscriptionIdentifier[]> => {
const inscriptions = await Promise.race([
getInscriptions(),
wait(WALLET_FETCH_INSRIPTIONS_TIMEOUT),
]);

if (inscriptions) {
return inscriptions;
}

const verifiedUTXOs = await postVerifyUtxoOrdinals(
filterDust(utxos),
address,
);
return verifiedUTXOs.filter((utxo) => utxo.inscription);
};

const data = useAPIQuery({
queryKey: [ORDINAL_KEY, utxos, address],
queryFn: fetchOrdinals,
enabled: Boolean(address) || enabled,
refetchInterval: 5 * ONE_MINUTE,
});

return data;
}
29 changes: 3 additions & 26 deletions src/app/hooks/api/useUTXOs.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,15 @@
import { ONE_MINUTE } from "@/app/constants";
import { useBTCWallet } from "@/app/context/wallet/BTCWalletProvider";
import { useAPIQuery } from "@/app/hooks/api/useApi";
import { useAppState } from "@/app/state";
import { filterOrdinals } from "@/utils/utxo";

export const UTXO_KEY = "UTXO";

export function useUTXOs({ enabled = true }: { enabled?: boolean } = {}) {
const { getUtxos, getInscriptions, address } = useBTCWallet();
const { ordinalsExcluded } = useAppState();

const fetchAvailableUTXOs = async () => {
if (!getUtxos || !address) {
return;
}

const mempoolUTXOs = await getUtxos(address);
// Return UTXOs without filtering if not required
if (!ordinalsExcluded) {
return mempoolUTXOs;
}

const filteredUTXOs = await filterOrdinals(
mempoolUTXOs,
address,
getInscriptions,
);

return filteredUTXOs;
};
const { getUtxos, address } = useBTCWallet();

const data = useAPIQuery({
queryKey: [UTXO_KEY, address, ordinalsExcluded],
queryFn: fetchAvailableUTXOs,
queryKey: [UTXO_KEY, address],
queryFn: () => getUtxos(address),
enabled: Boolean(getUtxos) && Boolean(address) && enabled,
refetchInterval: 5 * ONE_MINUTE,
});
Expand Down
58 changes: 43 additions & 15 deletions src/app/state/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {
useMemo,
useState as useReactState,
type PropsWithChildren,
} from "react";
import { useCallback, useMemo, useState, type PropsWithChildren } from "react";

import { useBTCTipHeight } from "@/app/hooks/api/useBTCTipHeight";
import { useOrdinals } from "@/app/hooks/api/useOrdinals";
import { useUTXOs } from "@/app/hooks/api/useUTXOs";
import { useVersions } from "@/app/hooks/api/useVersions";
import { GlobalParamsVersion } from "@/app/types/globalParams";
import { createStateUtils } from "@/utils/createStateUtils";
import { getCurrentGlobalParamsVersion } from "@/utils/globalParams";
import type { UTXO } from "@/utils/wallet/btc_wallet_provider";
import { filterDust } from "@/utils/wallet";
import type {
InscriptionIdentifier,
UTXO,
} from "@/utils/wallet/btc_wallet_provider";

import { DelegationState } from "./DelegationState";

Expand Down Expand Up @@ -51,31 +52,54 @@ const defaultVersionParams = {
};

export function AppState({ children }: PropsWithChildren) {
const [ordinalsExcluded, setOrdinalsExcluded] = useReactState(true);

const includeOrdinals = () => setOrdinalsExcluded(false);
const excludeOrdinals = () => setOrdinalsExcluded(true);
const [ordinalsExcluded, setOrdinalsExcluded] = useState(true);

// States
const {
data: availableUTXOs = [],
data: utxos = [],
isLoading: isUTXOLoading,
isError: isUTXOError,
} = useUTXOs();
const {
data: ordinals = [],
isLoading: isOrdinalLoading,
isError: isOrdinalError,
} = useOrdinals(utxos, {
enabled: !isUTXOLoading,
});
const {
data: versions,
isError: isVersionError,
isLoading: isVersionLoading,
isError: isVersionError,
} = useVersions();
const {
data: height,
isError: isHeightError,
isLoading: isHeightLoading,
isError: isHeightError,
} = useBTCTipHeight();

// Computed
const isLoading = isVersionLoading || isHeightLoading || isUTXOLoading;
const isError = isHeightError || isVersionError || isUTXOError;
const isLoading =
isVersionLoading || isHeightLoading || isUTXOLoading || isOrdinalLoading;
const isError =
isHeightError || isVersionError || isUTXOError || isOrdinalError;

const ordinalMap: Record<string, InscriptionIdentifier> = useMemo(
() =>
ordinals.reduce(
(acc, ordinal) => ({ ...acc, [ordinal.txid]: ordinal }),
{},
),
[ordinals],
);

const availableUTXOs = useMemo(() => {
if (isLoading) return [];

return ordinalsExcluded
? filterDust(utxos).filter((utxo) => !ordinalMap[utxo.txid])
: utxos;
}, [isLoading, ordinalsExcluded, utxos, ordinalMap]);

const totalBalance = useMemo(
() =>
Expand All @@ -91,6 +115,10 @@ export function AppState({ children }: PropsWithChildren) {
[versions, height],
);

// Handlers
const includeOrdinals = useCallback(() => setOrdinalsExcluded(false), []);
const excludeOrdinals = useCallback(() => setOrdinalsExcluded(true), []);

// Context
const context = useMemo(
() => ({
Expand Down
5 changes: 5 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function wait(ms: number): Promise<undefined> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Loading

0 comments on commit 061f71c

Please sign in to comment.