Skip to content

Commit

Permalink
feature: Resolve filter ordinals (#258)
Browse files Browse the repository at this point in the history
* feature: Ordinals filtering modal (#251)

* ordinals filtering modal

* certify

* update wording

* title and may contain

* resolve filter ordinals (main to dev)

* filter ordinals is a part of btc wallet provider

* rm should filter ordinals from useUTXOs

* TODO ordinals wording

* initial move

* rm unused comment

* rename var
  • Loading branch information
gbarkhatov authored Oct 29, 2024
1 parent e274321 commit 56ebe6f
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 12 deletions.
14 changes: 14 additions & 0 deletions src/app/components/Connect/ConnectSmall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Tooltip } from "react-tooltip";
import { useOnClickOutside } from "usehooks-ts";

import { useHealthCheck } from "@/app/hooks/useHealthCheck";
import { useAppState } from "@/app/state";
import { getNetworkConfig } from "@/config/network.config";
import { satoshiToBtc } from "@/utils/btcConversions";
import { maxDecimals } from "@/utils/maxDecimals";
Expand All @@ -30,6 +31,8 @@ export const ConnectSmall: React.FC<ConnectSmallProps> = ({
btcWalletBalanceSat,
onDisconnect,
}) => {
const { ordinalsExcluded, includeOrdinals, excludeOrdinals } = useAppState();

const [showMenu, setShowMenu] = useState(false);
const handleClickOutside = () => {
setShowMenu(false);
Expand Down Expand Up @@ -110,6 +113,17 @@ export const ConnectSmall: React.FC<ConnectSmallProps> = ({
<div className="flex flex-col">
<Hash value={address} address noFade fullWidth />
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Ordinals included</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={!ordinalsExcluded}
onChange={ordinalsExcluded ? includeOrdinals : excludeOrdinals}
/>
</label>
</div>
<button
className="btn btn-outline btn-sm"
onClick={() => {
Expand Down
16 changes: 16 additions & 0 deletions src/app/components/Connect/ConnectedSmall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FaBitcoin } from "react-icons/fa";
import { IoMdClose } from "react-icons/io";
import { useOnClickOutside } from "usehooks-ts";

import { useAppState } from "@/app/state";
import { getNetworkConfig } from "@/config/network.config";
import { satoshiToBtc } from "@/utils/btcConversions";
import { maxDecimals } from "@/utils/maxDecimals";
Expand All @@ -24,6 +25,8 @@ export const ConnectedSmall: React.FC<ConnectedSmallProps> = ({
btcWalletBalanceSat,
onDisconnect,
}) => {
const { ordinalsExcluded, includeOrdinals, excludeOrdinals } = useAppState();

const [showMenu, setShowMenu] = useState(false);
const handleClickOutside = () => {
setShowMenu(false);
Expand Down Expand Up @@ -73,6 +76,19 @@ export const ConnectedSmall: React.FC<ConnectedSmallProps> = ({
<div className="flex flex-col">
<Hash value={address} address noFade fullWidth />
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Ordinals included</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={!ordinalsExcluded}
onChange={
ordinalsExcluded ? includeOrdinals : excludeOrdinals
}
/>
</label>
</div>
<button
className="btn btn-outline btn-sm"
onClick={() => {
Expand Down
91 changes: 91 additions & 0 deletions src/app/components/Modals/FilterOrdinalsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useEffect, useState } from "react";
import { IoMdClose } from "react-icons/io";

import { useWalletConnection } from "@/app/context/wallet/WalletConnectionProvider";
import { useAppState } from "@/app/state";

import { GeneralModal } from "./GeneralModal";

interface FilterOrdinalsModalProps {}

export const FilterOrdinalsModal: React.FC<FilterOrdinalsModalProps> = ({}) => {
const [modalOpen, setModalOpen] = useState(false);
const handleClose = () => setModalOpen(false);

const { isConnected } = useWalletConnection();
const { ordinalsExcluded, includeOrdinals, excludeOrdinals } = useAppState();

useEffect(() => {
if (isConnected) {
setModalOpen(true);
}
}, [isConnected]);

return (
<GeneralModal open={modalOpen} onClose={handleClose}>
<div className="mb-4 flex items-center justify-between">
<h3 className="font-bold">
Ordinals, Bitcoin NFTs, Runes, Bitcoin inscriptions
</h3>
<button
className="btn btn-circle btn-ghost btn-sm"
onClick={handleClose}
>
<IoMdClose size={24} />
</button>
</div>
<div className="flex flex-col gap-4 md:max-w-[34rem]">
<div className="flex flex-col gap-4 leading-snug text-sm">
<p>
Your wallet may contain Bitcoin Ordinals, which are unique digital
assets. If you proceed without filtering, these Ordinals could be
included in future actions involving your balance.
</p>
<p>Please select:</p>
</div>
<div className="flex flex-col gap-4 rounded-xl bg-base-100 p-3 md:p-4">
<div className="form-control">
<label className="label cursor-pointer justify-start gap-2">
<input
type="radio"
name="ordinals-radio"
className="radio checked:bg-primary"
checked={!ordinalsExcluded}
onChange={includeOrdinals}
/>
<span className="label-text">
I want to <strong>include</strong> ordinals, bitcoin NFTs,
Ruins, and any other bitcoin inscriptions in my stakable bitcoin
balance. I understand that doing so can cause their complete and
permanent loss and agree that I am solely liable and responsible
for their loss. I have been warned.
</span>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer justify-start gap-2">
<input
type="radio"
name="ordinals-radio"
className="radio checked:bg-primary"
checked={ordinalsExcluded}
onChange={excludeOrdinals}
/>
<span className="label-text">
I would like to <strong>exclude</strong> ordinals, bitcoin NFTs,
Ruins, and any other bitcoin inscriptions in my stakable bitcoin
balance
</span>
</label>
</div>
</div>
<p className="text-xs opacity-50">
* You can change this setting later if needed
</p>
<button className="btn-primary btn flex-1" onClick={handleClose}>
Confirm
</button>
</div>
</GeneralModal>
);
};
2 changes: 1 addition & 1 deletion src/app/context/wallet/BTCWalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => {
const [address, setAddress] = useState("");

const { showError } = useError();
const { open, disconnect, isConnected, providers } = useWalletConnection();
const { open, isConnected, providers } = useWalletConnection();

const btcDisconnect = useCallback(() => {
setBTCWalletProvider(undefined);
Expand Down
1 change: 1 addition & 0 deletions src/app/context/wallet/WalletConnectionProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const WalletConnectionProvider = ({ children }: PropsWithChildren) => {
<TomoContextProvider
bitcoinChains={bitcoinChains}
multiNetworkConnection={true}
// TODO change options (ordinals) wording as soon as it's available
cosmosChains={[
{
id: 1,
Expand Down
9 changes: 8 additions & 1 deletion src/app/hooks/api/useUTXOs.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
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,
Expand All @@ -24,7 +31,7 @@ export function useUTXOs({ enabled = true }: { enabled?: boolean } = {}) {
};

const data = useAPIQuery({
queryKey: [UTXO_KEY, address],
queryKey: [UTXO_KEY, address, ordinalsExcluded],
queryFn: fetchAvailableUTXOs,
enabled: Boolean(getUtxos) && Boolean(address) && enabled,
refetchInterval: 5 * ONE_MINUTE,
Expand Down
2 changes: 2 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DelegationTabs } from "./components/Delegations/DelegationTabs";
import { FAQ } from "./components/FAQ/FAQ";
import { Footer } from "./components/Footer/Footer";
import { Header } from "./components/Header/Header";
import { FilterOrdinalsModal } from "./components/Modals/FilterOrdinalsModal";
import { NetworkBadge } from "./components/NetworkBadge/NetworkBadge";
import { PersonalBalance } from "./components/PersonalBalance/PersonalBalance";
import { Staking } from "./components/Staking/Staking";
Expand All @@ -31,6 +32,7 @@ const Home = () => {
</div>
<FAQ />
<Footer />
<FilterOrdinalsModal />
</>
);
};
Expand Down
49 changes: 39 additions & 10 deletions src/app/state/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useMemo, type PropsWithChildren } from "react";
import {
useMemo,
useState as useReactState,
type PropsWithChildren,
} from "react";

import { useBTCTipHeight } from "@/app/hooks/api/useBTCTipHeight";
import { useUTXOs } from "@/app/hooks/api/useUTXOs";
Expand All @@ -22,15 +26,22 @@ export interface AppState {
firstActivationHeight: number;
isError: boolean;
isLoading: boolean;
ordinalsExcluded: boolean;
includeOrdinals: () => void;
excludeOrdinals: () => void;
}

const { StateProvider, useState } = createStateUtils<AppState>({
isLoading: false,
isError: false,
totalBalance: 0,
isApprochingNextVersion: false,
firstActivationHeight: 0,
});
const { StateProvider, useState: useApplicationState } =
createStateUtils<AppState>({
isLoading: false,
isError: false,
totalBalance: 0,
isApprochingNextVersion: false,
firstActivationHeight: 0,
ordinalsExcluded: true,
includeOrdinals: () => {},
excludeOrdinals: () => {},
});

const defaultVersionParams = {
isApprochingNextVersion: false,
Expand All @@ -40,6 +51,11 @@ const defaultVersionParams = {
};

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

const includeOrdinals = () => setOrdinalsExcluded(false);
const excludeOrdinals = () => setOrdinalsExcluded(true);

// States
const {
data: availableUTXOs = [],
Expand Down Expand Up @@ -84,8 +100,21 @@ export function AppState({ children }: PropsWithChildren) {
...versionInfo,
isError,
isLoading,
ordinalsExcluded,
includeOrdinals,
excludeOrdinals,
}),
[availableUTXOs, height, totalBalance, versionInfo, isError, isLoading],
[
availableUTXOs,
height,
totalBalance,
versionInfo,
isError,
isLoading,
ordinalsExcluded,
includeOrdinals,
excludeOrdinals,
],
);

const states = useMemo(
Expand All @@ -100,4 +129,4 @@ export function AppState({ children }: PropsWithChildren) {
return <StateProvider value={context}>{states}</StateProvider>;
}

export const useAppState = useState;
export const useAppState = useApplicationState;

0 comments on commit 56ebe6f

Please sign in to comment.