diff --git a/docs/WalletIntegration.md b/docs/WalletIntegration.md index c91e2108..449b7efc 100644 --- a/docs/WalletIntegration.md +++ b/docs/WalletIntegration.md @@ -56,9 +56,17 @@ export interface UTXO { scriptPubKey: string; } +export interface Inscription { + // output of the inscription in the format of `txid:vout` + output: string; +} + // supported networks export type Network = "mainnet" | "testnet" | "regtest" | "signet"; +// Default number of inscriptions to fetch in a single method call +export const DEFAULT_INSCRIPTION_LIMIT = 100; + /** * Abstract class representing a wallet provider. * Provides methods for connecting to a wallet, retrieving wallet information, signing transactions, and more. @@ -162,6 +170,12 @@ export abstract class WalletProvider { * @returns A promise that resolves to the block height. */ abstract getBTCTipHeight(): Promise; + + /** + * Retrieves the inscriptions for the connected wallet. + * @returns A promise that resolves to an array of inscriptions. + */ + abstract getInscriptions(): Promise; } ``` @@ -307,7 +321,6 @@ export class OKXWallet extends WalletProvider { }; // Mempool calls - getBalance = async (): Promise => { return await getAddressBalance(await this.getAddress()); }; @@ -328,6 +341,27 @@ export class OKXWallet extends WalletProvider { getBTCTipHeight = async (): Promise => { return await getTipHeight(); }; + + // Inscriptions(Ordinal/Runes/BRC-20 etc) + getInscriptions = async (): Promise => { + if (!this.okxWalletInfo) { + throw new Error("OKX Wallet not connected"); + } + const inscriptions: Inscription[] = []; + let cursor = 0; + while (true) { + const { list } = await this.bitcoinNetworkProvider.getInscriptions( + cursor, + DEFAULT_INSCRIPTION_LIMIT, + ); + inscriptions.push(...list); + if (list.length < DEFAULT_INSCRIPTION_LIMIT) { + break; + } + cursor += DEFAULT_INSCRIPTION_LIMIT; + } + return inscriptions; + }; } ``` @@ -401,7 +435,7 @@ export async function pushTx(txHex: string): Promise { * Returns the balance of an address. * @param address - The Bitcoin address in string format. * @returns A promise that resolves to the amount of satoshis that the address - * holds. + * holds. */ export async function getAddressBalance(address: string): Promise { const response = await fetch(addressInfoUrl(address)); diff --git a/package-lock.json b/package-lock.json index 1ba1c740..91f6c316 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-staking", - "version": "0.2.17", + "version": "0.2.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-staking", - "version": "0.2.17", + "version": "0.2.19", "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", "@keystonehq/animated-qr": "^0.8.6", diff --git a/package.json b/package.json index 784fc050..c3d2c0b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simple-staking", - "version": "0.2.17", + "version": "0.2.20", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/api/apiWrapper.ts b/src/app/api/apiWrapper.ts index 0cc22ab9..85742e18 100644 --- a/src/app/api/apiWrapper.ts +++ b/src/app/api/apiWrapper.ts @@ -5,6 +5,7 @@ export const apiWrapper = async ( url: string, generalErrorMessage: string, params?: any, + timeout?: number, ) => { let response; let handler; @@ -28,6 +29,9 @@ export const apiWrapper = async ( : { params, }, + { + timeout: timeout || 0, // 0 is no timeout + }, ); } catch (error) { if (axios.isAxiosError(error)) { diff --git a/src/app/api/postFilterOrdinals.ts b/src/app/api/postFilterOrdinals.ts new file mode 100644 index 00000000..306a452c --- /dev/null +++ b/src/app/api/postFilterOrdinals.ts @@ -0,0 +1,39 @@ +import { chunkArray } from "@/utils/chunkArray"; +import { UTXO } from "@/utils/wallet/wallet_provider"; + +import { apiWrapper } from "./apiWrapper"; + +export interface UtxoInfo { + txid: string; + vout: number; + inscription: boolean; +} + +const TIMEOUT_DURATION = 2000; // 2 seconds +const BATCH_SIZE = 30; + +export const postVerifyUtxoOrdinals = async ( + utxos: UTXO[], + address: string, +): Promise => { + const utxoChunks = chunkArray(utxos, BATCH_SIZE); + const responses = await Promise.all( + utxoChunks.map((chunk) => + apiWrapper( + "POST", + "/v1/ordinals/verify-utxos", + "Error verifying utxos ordinals", + { + address, + utxos: chunk.map((utxo) => ({ + txid: utxo.txid, + vout: utxo.vout, + })), + }, + TIMEOUT_DURATION, + ), + ), + ); + + return responses.flatMap((response) => response.data.data); +}; diff --git a/src/app/common/constants.ts b/src/app/common/constants.ts index 0482911b..12d1016d 100644 --- a/src/app/common/constants.ts +++ b/src/app/common/constants.ts @@ -1,2 +1,3 @@ export const OVERFLOW_TVL_WARNING_THRESHOLD = 0.8; export const OVERFLOW_HEIGHT_WARNING_THRESHOLD = 3; +export const UTXO_KEY = "UTXOs"; diff --git a/src/app/components/Connect/ConnectSmall.tsx b/src/app/components/Connect/ConnectSmall.tsx index 19bc0281..e0a5deae 100644 --- a/src/app/components/Connect/ConnectSmall.tsx +++ b/src/app/components/Connect/ConnectSmall.tsx @@ -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 = ({ onConnect, address, - balanceSat, + btcWalletBalanceSat, onDisconnect, }) => { const [showMenu, setShowMenu] = useState(false); @@ -43,11 +44,15 @@ export const ConnectSmall: React.FC = ({
-

- - {maxDecimals(satoshiToBtc(balanceSat), 8) || 0} {coinName} - -

+ {typeof btcWalletBalanceSat === "number" ? ( +

+ + {maxDecimals(satoshiToBtc(btcWalletBalanceSat), 8)} {coinName} + +

+ ) : ( + + )}
diff --git a/src/app/components/Connect/ConnectedSmall.tsx b/src/app/components/Connect/ConnectedSmall.tsx index b1d1cfe8..a4d258e9 100644 --- a/src/app/components/Connect/ConnectedSmall.tsx +++ b/src/app/components/Connect/ConnectedSmall.tsx @@ -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 = ({ address, - balanceSat, + btcWalletBalanceSat, onDisconnect, }) => { const [showMenu, setShowMenu] = useState(false); @@ -41,11 +42,16 @@ export const ConnectedSmall: React.FC = ({
-

- - {maxDecimals(satoshiToBtc(balanceSat), 8) || 0} {coinName} - -

+ {typeof btcWalletBalanceSat === "number" ? ( +

+ + {maxDecimals(satoshiToBtc(btcWalletBalanceSat), 8)}{" "} + {coinName} + +

+ ) : ( + + )}
diff --git a/src/app/components/FAQ/data/questions.ts b/src/app/components/FAQ/data/questions.ts index 16904a27..425c5487 100644 --- a/src/app/components/FAQ/data/questions.ts +++ b/src/app/components/FAQ/data/questions.ts @@ -79,6 +79,11 @@ export const questions = (coinName: string): Question[] => {

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.

`, }, + { + title: + "Is it ok to use a wallet holding fungible tokens built on Bitcoin (e.g. BRC-20/ARC-20/Runes)?", + content: `

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.

`, + }, ]; if (shouldDisplayTestingMsg()) { questionList.push({ diff --git a/src/app/components/Header/Header.tsx b/src/app/components/Header/Header.tsx index 637d18aa..cdbf5ff6 100644 --- a/src/app/components/Header/Header.tsx +++ b/src/app/components/Header/Header.tsx @@ -8,42 +8,44 @@ import { Logo } from "./Logo"; interface HeaderProps { onConnect: () => void; address: string; - balanceSat: number; + btcWalletBalanceSat?: number; onDisconnect: () => void; } export const Header: React.FC = ({ onConnect, address, - balanceSat, + btcWalletBalanceSat, onDisconnect, }) => { return (