Skip to content

Commit

Permalink
feature: BRC-20 protection (#374)
Browse files Browse the repository at this point in the history
* feat: filter ordinal UTXOs

---------

Co-authored-by: wjrjerome <[email protected]>
  • Loading branch information
gbarkhatov and jrwbabylonlab authored Jul 24, 2024
1 parent 83cb1f6 commit b0eae41
Show file tree
Hide file tree
Showing 19 changed files with 597 additions and 73 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "simple-staking",
"version": "0.2.17",
"version": "0.2.18",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
4 changes: 4 additions & 0 deletions src/app/api/apiWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const apiWrapper = async (
url: string,
generalErrorMessage: string,
params?: any,
timeout?: number,
) => {
let response;
let handler;
Expand All @@ -28,6 +29,9 @@ export const apiWrapper = async (
: {
params,
},
{
timeout: timeout || 0, // 0 is no timeout
},
);
} catch (error) {
if (axios.isAxiosError(error)) {
Expand Down
60 changes: 60 additions & 0 deletions src/app/api/postFilterOrdinals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { chunkArray } from "@/utils/chunkArray";
import { UTXO } from "@/utils/wallet/wallet_provider";

import { apiWrapper } from "./apiWrapper";

interface UtxoInfo {
txid: string;
vout: number;
inscription: boolean;
}

const TIMEOUT_DURATION = 2000; // 2 seconds
const BATCH_SIZE = 30;

export const postFilterOrdinals = async (
utxos: UTXO[],
address: string,
): Promise<UTXO[]> => {
try {
const utxoChunks = chunkArray(utxos, BATCH_SIZE);
const responses = await Promise.all(
utxoChunks.map((chunk) =>
apiWrapper(
"POST",
"/v1/ordinals/verify-utxos",
"Error filtering ordinals",
{
address,
utxos: chunk.map((utxo) => ({
txid: utxo.txid,
vout: utxo.vout,
})),
},
TIMEOUT_DURATION,
),
),
);

const allUtxoInfo = responses.flatMap((response) => response.data.data);

// turn the data into map with key of the `txid:vout`
const utxoInfoMap = allUtxoInfo.reduce(
(acc: Record<string, boolean>, u: UtxoInfo) => {
acc[getUTXOIdentifier(u)] = u.inscription;
return acc;
},
{},
);

// filter out the ordinals
return utxos.filter((utxo) => !utxoInfoMap[getUTXOIdentifier(utxo)]);
} catch (error) {
// in case if any error we return the original utxos
return utxos;
}
};

// helper function to get the identifier of a UTXO
const getUTXOIdentifier = (utxo: { txid: string; vout: number }) =>
`${utxo.txid}:${utxo.vout}`;
1 change: 1 addition & 0 deletions src/app/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const OVERFLOW_TVL_WARNING_THRESHOLD = 0.8;
export const OVERFLOW_HEIGHT_WARNING_THRESHOLD = 3;
export const UTXO_KEY = "UTXOs";
19 changes: 12 additions & 7 deletions src/app/components/Connect/ConnectSmall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import { maxDecimals } from "@/utils/maxDecimals";
import { trim } from "@/utils/trim";

import { Hash } from "../Hash/Hash";
import { LoadingSmall } from "../Loading/Loading";

interface ConnectSmallProps {
onConnect: () => void;
address: string;
balanceSat: number;
btcWalletBalanceSat?: number;
onDisconnect: () => void;
}

export const ConnectSmall: React.FC<ConnectSmallProps> = ({
onConnect,
address,
balanceSat,
btcWalletBalanceSat,
onDisconnect,
}) => {
const [showMenu, setShowMenu] = useState(false);
Expand All @@ -43,11 +44,15 @@ export const ConnectSmall: React.FC<ConnectSmallProps> = ({
<div className="flex items-center rounded-lg border border-base-200/75 p-2 pr-4">
<div className="flex items-center gap-1">
<FaBitcoin className="text-primary" />
<p>
<strong>
{maxDecimals(satoshiToBtc(balanceSat), 8) || 0} {coinName}
</strong>
</p>
{typeof btcWalletBalanceSat === "number" ? (
<p>
<strong>
{maxDecimals(satoshiToBtc(btcWalletBalanceSat), 8)} {coinName}
</strong>
</p>
) : (
<LoadingSmall text="Loading..." />
)}
</div>
</div>
<div className="relative right-[10px] flex items-center rounded-lg border border-primary bg-[#fdf2ec] p-2 dark:border-white dark:bg-base-200">
Expand Down
20 changes: 13 additions & 7 deletions src/app/components/Connect/ConnectedSmall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import { maxDecimals } from "@/utils/maxDecimals";
import { trim } from "@/utils/trim";

import { Hash } from "../Hash/Hash";
import { LoadingSmall } from "../Loading/Loading";

interface ConnectedSmallProps {
address: string;
balanceSat: number;
onDisconnect: () => void;
btcWalletBalanceSat?: number;
}

export const ConnectedSmall: React.FC<ConnectedSmallProps> = ({
address,
balanceSat,
btcWalletBalanceSat,
onDisconnect,
}) => {
const [showMenu, setShowMenu] = useState(false);
Expand All @@ -41,11 +42,16 @@ export const ConnectedSmall: React.FC<ConnectedSmallProps> = ({
<div className="flex items-center rounded-lg border border-base-200/75 p-2 pr-4 w-full">
<div className="flex items-center gap-1 w-full justify-center">
<FaBitcoin className="text-primary" />
<p>
<strong>
{maxDecimals(satoshiToBtc(balanceSat), 8) || 0} {coinName}
</strong>
</p>
{typeof btcWalletBalanceSat === "number" ? (
<p>
<strong>
{maxDecimals(satoshiToBtc(btcWalletBalanceSat), 8)}{" "}
{coinName}
</strong>
</p>
) : (
<LoadingSmall text="Loading..." />
)}
</div>
</div>
<div className="relative flex items-center rounded-lg border border-primary bg-[#fdf2ec] p-2 dark:border-white dark:bg-base-200">
Expand Down
5 changes: 5 additions & 0 deletions src/app/components/FAQ/data/questions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ export const questions = (coinName: string): Question[] => {
</ol><br />
<p>In summary, to stake S, you need S + Fs, and upon completion, you get S - Fw or S - Fu - Fw back, depending on whether you wait for expiration or unbond early.</p>`,
},
{
title:
"Is it ok to use a wallet holding fungible tokens built on Bitcoin (e.g. BRC-20/ARC-20/Runes)?",
content: `<p>No, this should be avoided. The fungible tokens built on Bitcoin ecosystem is still in its infancy and in an experimental phase. Software built for the detection of such tokens to avoid their misspending comes without a warranty and guarantees, making it risky to connect wallets owning such tokens to dApps.</p>`,
},
];
if (shouldDisplayTestingMsg()) {
questionList.push({
Expand Down
10 changes: 5 additions & 5 deletions src/app/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { Logo } from "./Logo";
interface HeaderProps {
onConnect: () => void;
address: string;
balanceSat: number;
btcWalletBalanceSat?: number;
onDisconnect: () => void;
}

export const Header: React.FC<HeaderProps> = ({
onConnect,
address,
balanceSat,
btcWalletBalanceSat,
onDisconnect,
}) => {
return (
Expand All @@ -31,17 +31,17 @@ export const Header: React.FC<HeaderProps> = ({
<ConnectSmall
onConnect={onConnect}
address={address}
balanceSat={balanceSat}
btcWalletBalanceSat={btcWalletBalanceSat}
onDisconnect={onDisconnect}
/>
<ThemeToggle />
</div>
<div
className={`container mx-auto flex w-full items-center gap-4 ${address ? "justify-end p-6 pt-0" : ""} md:hidden md:p-0`}
className={`${address && "justify-end p-6 pt-0"}container mx-auto flex w-full items-center gap-4 md:hidden md:p-0`}
>
<ConnectedSmall
address={address}
balanceSat={balanceSat}
btcWalletBalanceSat={btcWalletBalanceSat}
onDisconnect={onDisconnect}
/>
</div>
Expand Down
28 changes: 24 additions & 4 deletions src/app/components/Modals/ConnectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const ConnectModal: React.FC<ConnectModalProps> = ({
onConnect,
connectDisabled,
}) => {
const [accepted, setAccepted] = useState(false);
const [termsAccepted, setTermsAccepted] = useState(false);
const [noInscription, setNoInscription] = useState(false);
const [selectedWallet, setSelectedWallet] = useState<string>("");
const [mounted, setMounted] = useState(false);

Expand Down Expand Up @@ -139,8 +140,8 @@ export const ConnectModal: React.FC<ConnectModalProps> = ({
<input
type="checkbox"
className="checkbox-primary checkbox"
onChange={(e) => setAccepted(e.target.checked)}
checked={accepted}
onChange={(e) => setTermsAccepted(e.target.checked)}
checked={termsAccepted}
/>
<span className="label-text">
I certify that I have read and accept the updated{" "}
Expand All @@ -154,6 +155,20 @@ export const ConnectModal: React.FC<ConnectModalProps> = ({
</span>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer justify-start gap-2 rounded-xl bg-base-100 p-4">
<input
type="checkbox"
className="checkbox-primary checkbox"
onChange={(e) => setNoInscription(e.target.checked)}
checked={noInscription}
/>
<span className="label-text">
I certify that there are no Bitcoin inscriptions tokens in my
wallet.
</span>
</label>
</div>
<div className="my-4 flex flex-col gap-4">
<h3 className="text-center font-semibold">Choose wallet</h3>
<div className="grid max-h-[20rem] grid-cols-1 gap-4 overflow-y-auto">
Expand Down Expand Up @@ -216,7 +231,12 @@ export const ConnectModal: React.FC<ConnectModalProps> = ({
<button
className="btn-primary btn h-[2.5rem] min-h-[2.5rem] rounded-lg px-2 text-white"
onClick={handleConnect}
disabled={connectDisabled || !accepted || !selectedWallet}
disabled={
connectDisabled ||
!termsAccepted ||
!selectedWallet ||
!noInscription
}
>
<PiWalletBold size={20} />
Connect to {networkName} network
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Staking/Form/StakingAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { validateDecimalPoints } from "./validation/validation";
interface StakingAmountProps {
minStakingAmountSat: number;
maxStakingAmountSat: number;
btcWalletBalanceSat: number;
btcWalletBalanceSat?: number;
onStakingAmountSatChange: (inputAmountSat: number) => void;
reset: boolean;
}
Expand Down Expand Up @@ -52,6 +52,7 @@ export const StakingAmount: React.FC<StakingAmountProps> = ({
};

const handleBlur = (_e: FocusEvent<HTMLInputElement>) => {
if (!btcWalletBalanceSat) return;
setTouched(true);

if (value === "") {
Expand Down
Loading

0 comments on commit b0eae41

Please sign in to comment.