From b39a2b969dcaacf930c56484b45a57c9e7db363e Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 17 Apr 2025 11:55:55 +0545 Subject: [PATCH 01/29] dep: update styx-sdk --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6061db28..9f6cd0d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.0.1", + "@faktoryfun/styx-sdk": "^1.0.8", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", @@ -378,9 +378,9 @@ "license": "MIT" }, "node_modules/@faktoryfun/styx-sdk": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.0.1.tgz", - "integrity": "sha512-pYiRf+Mx9UfMSVpBtTXCmrHFP5BqPjBO7XciKQ12TG1WNn4C0dNmQJoBboM/bApWHTRZu40nf3W+IheiqHk9Og==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.0.8.tgz", + "integrity": "sha512-VAKrMeuJ80yeFdv4HAC9iUCRNZTI7W62KogL5LM1AlLaEcB6ntfy8eWbrtB7vHiEp9amkmjDNHjSZXJSME3dHw==", "license": "MIT", "dependencies": { "axios": "^1.6.2" diff --git a/package.json b/package.json index a613c746..cbbcbeeb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.0.1", + "@faktoryfun/styx-sdk": "^1.0.8", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", From 136f43823295b1a861e6f2c2d72bdd73dcb73740 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 17 Apr 2025 12:13:51 +0545 Subject: [PATCH 02/29] feat: deposit-form --- src/app/deposit/page.tsx | 9 + src/components/btc-deposit/DepositForm.tsx | 424 +++++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 src/app/deposit/page.tsx create mode 100644 src/components/btc-deposit/DepositForm.tsx diff --git a/src/app/deposit/page.tsx b/src/app/deposit/page.tsx new file mode 100644 index 00000000..a04f4fd3 --- /dev/null +++ b/src/app/deposit/page.tsx @@ -0,0 +1,9 @@ +import { DepositForm } from "@/components/btc-deposit/DepositForm"; +const page = () => { + return ( +
+ +
+ ); +}; +export default page; diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx new file mode 100644 index 00000000..84ed9945 --- /dev/null +++ b/src/components/btc-deposit/DepositForm.tsx @@ -0,0 +1,424 @@ +"use client"; + +import type React from "react"; + +import { useState } from "react"; +import { Bitcoin, Info, Copy, Check } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; +import { styxSDK, StyxSDK } from "@faktoryfun/styx-sdk"; + +export enum TransactionPriority { + Low = "low", + Medium = "medium", + High = "high", +} + +export type ConfirmationData = { + depositAmount: string; + depositAddress: string; + stxAddress: string; + opReturnHex: string; +}; + +export function DepositForm() { + const [amount, setAmount] = useState("0.0001"); + const [selectedPreset, setSelectedPreset] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + const [confirmationData, setConfirmationData] = + useState(null); + const [copiedField, setCopiedField] = useState(null); + const { toast } = useToast(); + + // Mock BTC price for USD conversion + const btcUsdPrice = 65000; + + // Helper functions + const formatUsdValue = (amount: number): string => { + if (!amount || amount <= 0) return "$0.00"; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(amount); + }; + + const calculateUsdValue = (btcAmount: string): number => { + if (!btcAmount) return 0; + const numAmount = Number.parseFloat(btcAmount); + return isNaN(numAmount) ? 0 : numAmount * btcUsdPrice; + }; + + const calculateFee = (btcAmount: string): string => { + if (!btcAmount || Number.parseFloat(btcAmount) <= 0) return "0.00000000"; + const numAmount = Number.parseFloat(btcAmount); + if (isNaN(numAmount)) return "0.00000600"; + return numAmount <= 0.002 ? "0.00003000" : "0.00006000"; + }; + + const handleAmountChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (value === "" || /^\d*\.?\d*$/.test(value)) { + setAmount(value); + setSelectedPreset(null); + } + }; + + const handlePresetClick = (presetAmount: string) => { + setAmount(presetAmount); + setSelectedPreset(presetAmount); + }; + + const handleMaxClick = () => { + // In a real implementation, this would calculate the max amount based on the user's BTC balance + // For now, we'll just set a mock value + const maxAmount = "0.05"; + setAmount(maxAmount); + setSelectedPreset("max"); + }; + + const handleCopyToClipboard = (text: string, field: string) => { + navigator.clipboard.writeText(text).then( + () => { + setCopiedField(field); + setTimeout(() => setCopiedField(null), 2000); + }, + (err) => { + console.error("Could not copy text: ", err); + } + ); + }; + + const prepareTransaction = async () => { + try { + setIsLoading(true); + + // Get user addresses + const userAddress = getStacksAddress(); + const btcAddress = getBitcoinAddress(); + + if (!userAddress || !btcAddress) { + throw new Error("Wallet not connected or addresses not found"); + } + + // This would be your actual SDK call + const transactionData = await styxSDK.prepareTransaction({ + amount: amount, // BTC amount as string + userAddress: userAddress, // STX address + btcAddress: btcAddress, // BTC address + feePriority: TransactionPriority.Medium, + walletProvider: "xverse", // "leather" or "xverse" + }); + + console.log("Transaction prepared:", transactionData); + + // Set confirmation data + setConfirmationData({ + depositAmount: amount, + depositAddress: transactionData.depositAddress, + stxAddress: userAddress, + opReturnHex: transactionData.opReturnData, + }); + + // Show confirmation screen + setShowConfirmation(true); + + return transactionData; + } catch (error) { + console.error("Error preparing transaction:", error); + toast({ + title: "Error", + description: + error instanceof Error + ? error.message + : "Failed to prepare transaction", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const handleBackToDeposit = () => { + setShowConfirmation(false); + setConfirmationData(null); + }; + + const presetAmounts = ["0.001", "0.005", "0.01"]; + const presetLabels = ["0.001 BTC", "0.005 BTC", "0.01 BTC"]; + + if (showConfirmation && confirmationData) { + return ( + + + + + Deposit Confirmation + + + Please review your deposit details below + + + +
+
+ Amount: + + {confirmationData.depositAmount} BTC + +
+
+ USD Value: + + {formatUsdValue( + calculateUsdValue(confirmationData.depositAmount) + )} + +
+
+ + STX Address: + +
+ + {confirmationData.stxAddress} + + +
+
+
+ +
+
+ Deposit Address: + +
+
+ {confirmationData.depositAddress} +
+
+ +
+
+ OP_RETURN Data: + +
+
+ {confirmationData.opReturnHex} +
+
+ +
+

+ Please send exactly {confirmationData.depositAmount} BTC to the + deposit address above. +

+
+
+ + + + +
+ ); + } + + return ( + + + + + Bitcoin Deposit + + + +
+
+
+ + Bitcoin +
+ + {formatUsdValue(calculateUsdValue(amount))} + +
+ +
+ + + BTC + +
+ +
+ {presetAmounts.map((presetAmount, index) => ( + + ))} + +
+
+ +
+
+ + Estimated time + + + 1 Block ~ 10 min + +
+
+ Service fee + + {amount && Number.parseFloat(amount) > 0 && btcUsdPrice + ? `${formatUsdValue( + Number.parseFloat(calculateFee(amount)) * btcUsdPrice + )} ~ ${calculateFee(amount)} BTC` + : "$0.00 ~ 0.00000000 BTC"} + +
+
+ + Pool liquidity + + + $650,000 ~ 10.00000000 BTC + +
+
+ + + + +
+ How it works + +
+
+ +

+ Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin state + reading. No intermediaries or multi-signature scheme needed. + Trustless. Fast. Secure. +

+
+
+
+
+ + + +
+ ); +} From 682c4de30bdb478d880afd855b46da8298dab070 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 17 Apr 2025 12:22:44 +0545 Subject: [PATCH 03/29] feat: helper hooks --- src/hooks/deposit/useResolveBnsOrAddress.ts | 19 +++++++++++++ src/hooks/deposit/useSdkAllDepositsHistory.ts | 18 ++++++++++++ src/hooks/deposit/useSdkBtcPrice.ts | 28 +++++++++++++++++++ src/hooks/deposit/useSdkDepositHistory.ts | 20 +++++++++++++ src/hooks/deposit/useSdkPoolStatus.ts | 18 ++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 src/hooks/deposit/useResolveBnsOrAddress.ts create mode 100644 src/hooks/deposit/useSdkAllDepositsHistory.ts create mode 100644 src/hooks/deposit/useSdkBtcPrice.ts create mode 100644 src/hooks/deposit/useSdkDepositHistory.ts create mode 100644 src/hooks/deposit/useSdkPoolStatus.ts diff --git a/src/hooks/deposit/useResolveBnsOrAddress.ts b/src/hooks/deposit/useResolveBnsOrAddress.ts new file mode 100644 index 00000000..9b74766c --- /dev/null +++ b/src/hooks/deposit/useResolveBnsOrAddress.ts @@ -0,0 +1,19 @@ +// hooks/useResolveBnsOrAddress.ts +import { useQuery } from "@tanstack/react-query"; + +// In a real application, this would call an API to resolve BNS names +const useResolveBnsOrAddress = (address: string) => { + return useQuery({ + queryKey: ["resolveAddress", address], + queryFn: async () => { + // Mock implementation + return { + resolvedValue: address.startsWith("SP") ? address : null, + }; + }, + enabled: !!address, + staleTime: 1000 * 60 * 60, // 1 hour + }); +}; + +export default useResolveBnsOrAddress; diff --git a/src/hooks/deposit/useSdkAllDepositsHistory.ts b/src/hooks/deposit/useSdkAllDepositsHistory.ts new file mode 100644 index 00000000..371395f4 --- /dev/null +++ b/src/hooks/deposit/useSdkAllDepositsHistory.ts @@ -0,0 +1,18 @@ +// hooks/useSdkAllDepositsHistory.ts +import { useQuery } from "@tanstack/react-query"; +import { styxSDK } from "@faktoryfun/styx-sdk"; + +const useSdkAllDepositsHistory = () => { + return useQuery({ + queryKey: ["allDepositsHistory"], + queryFn: async () => { + console.log("Fetching all deposits history..."); + const data = await styxSDK.getAllDepositsHistory(); + console.log("Received all deposits history:", data); + return data || []; + }, + staleTime: 60000, // 1 minute + }); +}; + +export default useSdkAllDepositsHistory; diff --git a/src/hooks/deposit/useSdkBtcPrice.ts b/src/hooks/deposit/useSdkBtcPrice.ts new file mode 100644 index 00000000..1ee937ae --- /dev/null +++ b/src/hooks/deposit/useSdkBtcPrice.ts @@ -0,0 +1,28 @@ +// hooks/useSdkBtcPrice.ts +import { useQuery } from "@tanstack/react-query"; +import { styxSDK } from "@faktoryfun/styx-sdk"; + +export const useSdkBtcPrice = () => { + return useQuery({ + queryKey: ["btcPrice"], + queryFn: async () => { + console.log("Fetching BTC price from SDK..."); + const price = await styxSDK.getBTCPrice(); + console.log("Received BTC price:", price); + return price; + }, + staleTime: 5 * 60 * 1000, // 5 minutes + }); +}; + +export const useFormattedBtcPrice = () => { + const query = useSdkBtcPrice(); + return { + price: query.data, + error: query.error, + isLoading: query.isLoading, + refetch: query.refetch, + }; +}; + +export default useSdkBtcPrice; diff --git a/src/hooks/deposit/useSdkDepositHistory.ts b/src/hooks/deposit/useSdkDepositHistory.ts new file mode 100644 index 00000000..b57782e1 --- /dev/null +++ b/src/hooks/deposit/useSdkDepositHistory.ts @@ -0,0 +1,20 @@ +// hooks/useSdkDepositHistory.ts +import { useQuery } from "@tanstack/react-query"; +import { styxSDK } from "@faktoryfun/styx-sdk"; + +const useSdkDepositHistory = (userAddress: string | null) => { + return useQuery({ + queryKey: ["depositHistory", userAddress], + queryFn: async () => { + if (!userAddress) return []; + console.log("Fetching deposit history for:", userAddress); + const data = await styxSDK.getDepositHistory(userAddress); + console.log("Received deposit history:", data); + return data || []; + }, + enabled: !!userAddress, + staleTime: 60000, // 1 minute + }); +}; + +export default useSdkDepositHistory; diff --git a/src/hooks/deposit/useSdkPoolStatus.ts b/src/hooks/deposit/useSdkPoolStatus.ts new file mode 100644 index 00000000..77d8c240 --- /dev/null +++ b/src/hooks/deposit/useSdkPoolStatus.ts @@ -0,0 +1,18 @@ +// hooks/useSdkPoolStatus.ts +import { useQuery } from "@tanstack/react-query"; +import { styxSDK } from "@faktoryfun/styx-sdk"; + +const useSdkPoolStatus = () => { + return useQuery({ + queryKey: ["poolStatus"], + queryFn: async () => { + console.log("Fetching pool status from SDK..."); + const data = await styxSDK.getPoolStatus(); + console.log("Received pool status:", data); + return data; + }, + staleTime: 60000, // 1 minute + }); +}; + +export default useSdkPoolStatus; From 7f8fef8948943602b7dc47a59f8a969130216068 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 17 Apr 2025 12:30:26 +0545 Subject: [PATCH 04/29] dep: sats-connect --- package-lock.json | 145 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 9f6cd0d0..a5ba6975 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "react-markdown": "^9.0.1", "recharts": "^2.15.0", "remark-gfm": "^4.0.0", + "sats-connect": "^3.4.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "zustand": "^5.0.2" @@ -2006,6 +2007,42 @@ "dev": true, "license": "MIT" }, + "node_modules/@sats-connect/core": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.6.2.tgz", + "integrity": "sha512-/vQsg6OnYA2+BN4VHi3xHPJYYz4K3ENjVfCjgK4RqAaMu14gZSaZEtcDQgyfuEmI4Hi3vI/yNqbJ3R8yC4anlw==", + "license": "ISC", + "dependencies": { + "axios": "1.8.4", + "bitcoin-address-validation": "2.2.3", + "buffer": "6.0.3", + "jsontokens": "4.0.1", + "valibot": "0.42.1" + } + }, + "node_modules/@sats-connect/make-default-provider-config": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@sats-connect/make-default-provider-config/-/make-default-provider-config-0.0.10.tgz", + "integrity": "sha512-BBot3Ofa2J7OwXprgYPD4C8dppX4nnPxj4FXWq1H7fDsvwJmW4sAnfmnAIzwmyWZJOR2uZqtTkXAA08sVkoN5g==", + "dependencies": { + "@sats-connect/ui": "^0.0.6", + "bowser": "^2.11.0" + }, + "peerDependencies": { + "@sats-connect/core": "*", + "typescript": "^5.0.0" + } + }, + "node_modules/@sats-connect/make-default-provider-config/node_modules/@sats-connect/ui": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@sats-connect/ui/-/ui-0.0.6.tgz", + "integrity": "sha512-H3bFFhr9CcY1oNosNi/QJszmMHSht4U19bUWfM3vzayAKgV4ebY6iUnRK5g3p2rVLLWVzlpaw1J9m+7JWwyBfA==" + }, + "node_modules/@sats-connect/ui": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@sats-connect/ui/-/ui-0.0.7.tgz", + "integrity": "sha512-dq02JxvTSAkfgFzEz4iWDSamm6Dte1omzxK0F1yytRZbIrbjjz1KmlMHM+uuxnFN9+EzHIHNsA4aS2dEDwh0xw==" + }, "node_modules/@scure/base": { "version": "1.1.9", "license": "MIT", @@ -4336,6 +4373,15 @@ "version": "4.0.0", "license": "MIT" }, + "node_modules/base58-js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-1.0.5.tgz", + "integrity": "sha512-LkkAPP8Zu+c0SVNRTRVDyMfKVORThX+rCViget00xdgLRrKkClCTz1T7cIrpr69ShwV5XJuuoZvMvJ43yURwkA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/base64-js": { "version": "1.5.1", "funding": [ @@ -4354,6 +4400,12 @@ ], "license": "MIT" }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" + }, "node_modules/bin-links": { "version": "5.0.0", "dev": true, @@ -4388,12 +4440,29 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/bitcoin-address-validation": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz", + "integrity": "sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg==", + "license": "MIT", + "dependencies": { + "base58-js": "^1.0.0", + "bech32": "^2.0.0", + "sha256-uint8array": "^0.10.3" + } + }, "node_modules/blake3-wasm": { "version": "2.1.5", "dev": true, "license": "MIT", "peer": true }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -4420,6 +4489,30 @@ "base-x": "^4.0.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "dev": true, @@ -6737,6 +6830,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "dev": true, @@ -9984,6 +10097,17 @@ "license": "MIT", "peer": true }, + "node_modules/sats-connect": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-3.4.0.tgz", + "integrity": "sha512-nm7uLwY7CEr8EWChXyNwibakI5ebCo8fJmcqTEOrQMSjJNUk/tTuyP7HHVV4prlE1g666e1I3wbB+J7dUJjS5A==", + "license": "MIT", + "dependencies": { + "@sats-connect/core": "0.6.2", + "@sats-connect/make-default-provider-config": "0.0.10", + "@sats-connect/ui": "0.0.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "license": "MIT", @@ -10064,6 +10188,12 @@ "license": "ISC", "peer": true }, + "node_modules/sha256-uint8array": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.7.tgz", + "integrity": "sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "license": "MIT", @@ -11010,7 +11140,6 @@ }, "node_modules/typescript": { "version": "5.7.2", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11243,6 +11372,20 @@ "license": "MIT", "peer": true }, + "node_modules/valibot": { + "version": "0.42.1", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz", + "integrity": "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/varuint-bitcoin": { "version": "1.1.2", "license": "MIT", diff --git a/package.json b/package.json index cbbcbeeb..2ebd0910 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "react-markdown": "^9.0.1", "recharts": "^2.15.0", "remark-gfm": "^4.0.0", + "sats-connect": "^3.4.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "zustand": "^5.0.2" From 2849027cdea643d3c9dc879f228a743c70a9f313 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 17 Apr 2025 13:24:43 +0545 Subject: [PATCH 05/29] feat: transaction-confirmation --- .../btc-deposit/TransactionConfirmation.tsx | 788 ++++++++++++++++++ 1 file changed, 788 insertions(+) create mode 100644 src/components/btc-deposit/TransactionConfirmation.tsx diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx new file mode 100644 index 00000000..22b490df --- /dev/null +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -0,0 +1,788 @@ +"use client"; + +import { useState } from "react"; +import { hex } from "@scure/base"; +import * as btc from "@scure/btc-signer"; +import { styxSDK, TransactionPriority } from "@faktoryfun/styx-sdk"; +import { request as xverseRequest } from "sats-connect"; +import { useToast } from "@/hooks/use-toast"; +import { ArrowLeft, Copy, Check, AlertTriangle } from "lucide-react"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { cn } from "@/lib/utils"; +import type { ConfirmationData } from "./DepositForm"; + +// Add this to fix the window.LeatherProvider type error +declare global { + interface Window { + LeatherProvider: any; + } +} + +interface TransactionConfirmationProps { + confirmationData: ConfirmationData; + open: boolean; + onClose: () => void; + feePriority: TransactionPriority; + setFeePriority: (priority: TransactionPriority) => void; + userAddress: string; + btcAddress: string; + activeWalletProvider: "leather" | "xverse" | null; +} + +interface XverseSignPsbtResponse { + status: "success" | "error"; + result?: { + psbt: string; + txid: string; + }; + error?: { + code?: string; + message?: string; + }; +} + +export default function TransactionConfirmation({ + confirmationData, + open, + onClose, + feePriority, + setFeePriority, + userAddress, + btcAddress, + activeWalletProvider, +}: TransactionConfirmationProps) { + const { toast } = useToast(); + const [btcTxStatus, setBtcTxStatus] = useState< + "idle" | "pending" | "success" | "error" + >("idle"); + const [btcTxId, setBtcTxId] = useState(""); + const [currentDepositId, setCurrentDepositId] = useState(null); + const [copied, setCopied] = useState>({}); + + const [feeEstimates, setFeeEstimates] = useState({ + low: { rate: 1, fee: 0, time: "30 min" }, + medium: { rate: 3, fee: 0, time: "~20 min" }, + high: { rate: 5, fee: 0, time: "~10 min" }, + }); + + const isP2SHAddress = (address: string): boolean => { + return address.startsWith("3"); + }; + + const copyToClipboard = async (text: string, field: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied({ ...copied, [field]: true }); + + toast({ + title: "Copied", + description: "The information has been copied to your clipboard", + }); + + setTimeout(() => { + setCopied({ ...copied, [field]: false }); + }, 2000); + } catch (err) { + console.error("Failed to copy text: ", err); + toast({ + title: "Failed to copy", + description: "Please try again or copy manually", + variant: "destructive", + }); + } + }; + + const executeBitcoinTransaction = async (): Promise => { + console.log( + "Starting transaction with activeWalletProvider:", + activeWalletProvider + ); + + // Check if wallet is connected + if (!activeWalletProvider) { + toast({ + title: "No wallet connected", + description: "Please connect a wallet before proceeding", + variant: "destructive", + }); + return; + } + + const feeRates = await styxSDK.getFeeEstimates(); + const selectedFeeRate = feeRates[feePriority]; + console.log( + `Using ${feePriority} priority fee rate: ${selectedFeeRate} sat/vB` + ); + + if (!confirmationData) { + toast({ + title: "Error", + description: "Missing transaction data", + variant: "destructive", + }); + return; + } + + // Begin Bitcoin transaction process + setBtcTxStatus("pending"); + + try { + // Create deposit record + console.log("Creating deposit with data:", { + btcAmount: Number.parseFloat(confirmationData.depositAmount), + stxReceiver: userAddress || "", + btcSender: btcAddress || "", + }); + + // Create deposit record which will update pool status (reduce estimated available) + const depositId = await styxSDK.createDeposit({ + btcAmount: Number.parseFloat(confirmationData.depositAmount), + stxReceiver: userAddress || "", + btcSender: btcAddress || "", + }); + console.log("Create deposit depositId:", depositId); + + // Store deposit ID for later use + setCurrentDepositId(depositId); + + try { + if ( + activeWalletProvider === "leather" && + (typeof window === "undefined" || !window.LeatherProvider) + ) { + throw new Error("Leather wallet is not installed or not accessible"); + } + + console.log( + "Window object has LeatherProvider:", + !!window.LeatherProvider + ); + console.log("Full window object keys:", Object.keys(window)); + + if (!userAddress) { + throw new Error("STX address is missing or invalid"); + } + + if (activeWalletProvider === "leather") { + console.log("About to use LeatherProvider:", window.LeatherProvider); + } + + // Use the BTC address from context + if (!btcAddress) { + throw new Error("Could not find a valid BTC address in wallet"); + } + + const senderBtcAddress = btcAddress; + console.log("Using BTC address from context:", senderBtcAddress); + + // Get a transaction prepared for signing + console.log("Getting prepared transaction from SDK..."); + const preparedTransaction = await styxSDK.prepareTransaction({ + amount: confirmationData.depositAmount, + userAddress, + btcAddress, + feePriority, + walletProvider: activeWalletProvider, + }); + + // Here, update fee estimates from the prepared transaction + setFeeEstimates({ + low: { + rate: preparedTransaction.feeRate, + fee: preparedTransaction.fee, + time: "30 min", + }, + medium: { + rate: preparedTransaction.feeRate, + fee: preparedTransaction.fee, + time: "~20 min", + }, + high: { + rate: preparedTransaction.feeRate, + fee: preparedTransaction.fee, + time: "~10 min", + }, + }); + + // Execute transaction with prepared data + console.log("Creating transaction with SDK..."); + const transactionData = await styxSDK.executeTransaction({ + depositId, + preparedData: { + utxos: preparedTransaction.utxos, + opReturnData: preparedTransaction.opReturnData, + depositAddress: preparedTransaction.depositAddress, + fee: preparedTransaction.fee, + changeAmount: preparedTransaction.changeAmount, + amountInSatoshis: preparedTransaction.amountInSatoshis, + feeRate: preparedTransaction.feeRate, + inputCount: preparedTransaction.inputCount, + outputCount: preparedTransaction.outputCount, + inscriptionCount: preparedTransaction.inscriptionCount, + }, + walletProvider: activeWalletProvider, + btcAddress: senderBtcAddress, + }); + + console.log("Transaction execution prepared:", transactionData); + + // Create a transaction object from the PSBT + let tx = new btc.Transaction({ + allowUnknownOutputs: true, + allowUnknownInputs: true, + disableScriptCheck: false, + }); + + // Load the base transaction from PSBT + tx = btc.Transaction.fromPSBT(hex.decode(transactionData.txPsbtHex)); + + // Handle P2SH for Xverse which requires frontend handling + const isP2sh = isP2SHAddress(senderBtcAddress); + if ( + isP2sh && + activeWalletProvider === "xverse" && + transactionData.needsFrontendInputHandling + ) { + console.log("Adding P2SH inputs specifically for Xverse"); + + // Only for P2SH + Xverse, do we need to add inputs - in all other cases the backend handled it + for (const utxo of preparedTransaction.utxos) { + try { + // First, try to get the account (which might fail if we don't have permission) + console.log("Trying to get wallet account..."); + let walletAccount = await (xverseRequest as any)( + "wallet_getAccount", + null + ); + + // If we get an access denied error, we need to request permissions + if ( + walletAccount.status === "error" && + walletAccount.error.code === -32002 + ) { + console.log("Access denied. Requesting permissions..."); + + // Request permissions using wallet_requestPermissions as shown in the docs + const permissionResponse = await (xverseRequest as any)( + "wallet_requestPermissions", + null + ); + console.log("Permission response:", permissionResponse); + + // If the user granted permission, try again to get the account + if (permissionResponse.status === "success") { + console.log( + "Permission granted. Trying to get wallet account again..." + ); + walletAccount = await (xverseRequest as any)( + "wallet_getAccount", + null + ); + } else { + throw new Error("User declined to grant permissions"); + } + } + + console.log("Wallet account response:", walletAccount); + + if ( + walletAccount.status === "success" && + walletAccount.result.addresses + ) { + // Find the payment address that matches our sender address + const paymentAddress = ( + walletAccount.result as any + ).addresses.find( + (addr: any) => + addr.address === senderBtcAddress && + addr.purpose === "payment" + ); + + if (paymentAddress && paymentAddress.publicKey) { + console.log( + "Found matching public key for P2SH address:", + paymentAddress.publicKey + ); + + // Create P2SH-P2WPKH from public key as shown in documentation + const publicKeyBytes = hex.decode(paymentAddress.publicKey); + const p2wpkh = btc.p2wpkh(publicKeyBytes, btc.NETWORK); + const p2sh = btc.p2sh(p2wpkh, btc.NETWORK); + + // Add input with redeemScript + tx.addInput({ + txid: utxo.txid, + index: utxo.vout, + witnessUtxo: { + script: p2sh.script, + amount: BigInt(utxo.value), + }, + redeemScript: p2sh.redeemScript, + }); + } else { + throw new Error( + "Could not find payment address with public key" + ); + } + } else { + throw new Error("Failed to get wallet account info"); + } + } catch (err) { + console.error("Error getting wallet account info:", err); + throw new Error( + "P2SH address requires access to the public key. Please use a SegWit address (starting with 'bc1') or grant necessary permissions." + ); + } + } + } + + // Extract transaction details from response + const { transactionDetails } = transactionData; + console.log("Transaction summary:", transactionDetails); + + // Generate PSBT and request signing + const txPsbt = tx.toPSBT(); + const finalTxPsbtHex = hex.encode(txPsbt); + const finalTxPsbtBase64 = Buffer.from(finalTxPsbtHex, "hex").toString( + "base64" + ); + + let txid; + + console.log("Wallet-specific flow for:", activeWalletProvider); + + if (activeWalletProvider === "leather") { + // Leather wallet flow + const requestParams = { + hex: finalTxPsbtHex, + network: "mainnet", + broadcast: false, + allowedSighash: [btc.SigHash.ALL], + allowUnknownOutputs: true, + }; + + if (!window.LeatherProvider) { + throw new Error( + "Leather wallet provider not found on window object" + ); + } + + // Send the signing request to Leather + const signResponse = await window.LeatherProvider.request( + "signPsbt", + requestParams + ); + + if ( + !signResponse || + !signResponse.result || + !signResponse.result.hex + ) { + throw new Error( + "Leather wallet did not return a valid signed PSBT" + ); + } + + // We get the hex of the signed PSBT back, finalize it + const signedPsbtHex = signResponse.result.hex; + const signedTx = btc.Transaction.fromPSBT(hex.decode(signedPsbtHex)); + signedTx.finalize(); + const finalTxHex = hex.encode(signedTx.extract()); + + // Manually broadcast the transaction + const broadcastResponse = await fetch( + "https://mempool.space/api/tx", + { + method: "POST", + headers: { + "Content-Type": "text/plain", + }, + body: finalTxHex, + } + ); + + if (!broadcastResponse.ok) { + const errorText = await broadcastResponse.text(); + throw new Error(`Failed to broadcast transaction: ${errorText}`); + } + + txid = await broadcastResponse.text(); + } else if (activeWalletProvider === "xverse") { + console.log("Executing Xverse transaction flow"); + console.log("xverseRequest function type:", typeof xverseRequest); + // Xverse wallet flow + try { + console.log("Starting Xverse PSBT signing flow..."); + + // Add all input addresses from our transaction + const inputAddresses: Record = {}; + inputAddresses[senderBtcAddress] = Array.from( + { length: preparedTransaction.utxos.length }, + (_, i) => i + ); + + console.log("Input addresses for Xverse:", inputAddresses); + console.log( + "PSBT Base64 (first 100 chars):", + finalTxPsbtBase64.substring(0, 100) + "..." + ); + + // Prepare request params + const xverseParams = { + psbt: finalTxPsbtBase64, + signInputs: inputAddresses, + broadcast: true, // Let Xverse handle broadcasting + allowedSighash: [ + btc.SigHash.ALL, + btc.SigHash.NONE, + btc.SigHash.SINGLE, + btc.SigHash.DEFAULT_ANYONECANPAY, + ], // More complete set of sighash options + options: { + allowUnknownInputs: true, + allowUnknownOutputs: true, + }, + }; + + // For P2SH addresses with Xverse, we need to add a special note in the logs + if (isP2SHAddress(senderBtcAddress)) { + console.log("Using P2SH-specific params for Xverse"); + console.log( + "P2SH address detected, relying on Xverse's internal handling" + ); + } + + console.log( + "Calling Xverse request with params:", + JSON.stringify(xverseParams, null, 2) + ); + + const response = (await xverseRequest( + "signPsbt", + xverseParams + )) as XverseSignPsbtResponse; + + console.log( + "Full Xverse response:", + JSON.stringify(response, null, 2) + ); + + if (response.status !== "success") { + console.error( + "Xverse signing failed with status:", + response.status + ); + console.error("Xverse error details:", response.error); + throw new Error( + `Xverse signing failed: ${ + response.error?.message || "Unknown error" + }` + ); + } + + // Fix the txid property access + if (!response.result?.txid) { + console.error("No txid in successful Xverse response:", response); + throw new Error("No transaction ID returned from Xverse"); + } + + txid = response.result.txid; + console.log("Successfully got txid from Xverse:", txid); + } catch (err) { + console.error("Detailed error with Xverse signing:", err); + console.error("Error type:", typeof err); + if (err instanceof Error) { + console.error("Error name:", err.name); + console.error("Error message:", err.message); + console.error("Error stack:", err.stack); + } + throw err; + } + } else { + throw new Error("No compatible wallet provider detected"); + } + + console.log("Transaction successfully broadcast with txid:", txid); + + // Update the deposit record with the transaction ID + console.log( + "Attempting to update deposit with ID:", + depositId, + "Type:", + typeof depositId + ); + + try { + console.log( + "About to update deposit with ID:", + depositId, + "and txid:", + txid + ); + console.log("Update data:", { + id: depositId, + data: { btcTxId: txid, status: "broadcast" }, + }); + + const updateResult = await styxSDK.updateDepositStatus({ + id: depositId, + data: { + btcTxId: txid, + status: "broadcast", + }, + }); + + console.log( + "Successfully updated deposit:", + JSON.stringify(updateResult, null, 2) + ); + } catch (error) { + console.error("Error updating deposit with ID:", depositId); + console.error("Update error details:", error); + if (error instanceof Error) { + console.error("Error name:", error.name); + console.error("Error message:", error.message); + console.error("Error stack:", error.stack); + } + } + + // Update state with success + setBtcTxStatus("success"); + setBtcTxId(txid); + + // Show success message + toast({ + title: "Deposit Initiated", + description: `Your Bitcoin transaction has been sent successfully with txid: ${txid.substring( + 0, + 10 + )}...`, + }); + + // Close confirmation dialog + onClose(); + } catch (err: any) { + const error = err as Error; + console.error("Error in Bitcoin transaction process:", error); + setBtcTxStatus("error"); + + // Update deposit as canceled if wallet interaction failed + await styxSDK.updateDepositStatus({ + id: depositId, + data: { + status: "canceled", + }, + }); + + toast({ + title: "Error", + description: + error.message || + "Failed to process Bitcoin transaction. Please try again.", + variant: "destructive", + }); + } + } catch (err: any) { + const error = err as Error; + console.error("Error creating deposit record:", error); + setBtcTxStatus("error"); + + toast({ + title: "Error", + description: "Failed to initiate deposit. Please try again.", + variant: "destructive", + }); + } + }; + + const formatAddress = (address: string): string => { + if (!address) return ""; + return `${address.slice(0, 8)}...${address.slice(-8)}`; + }; + + return ( + !open && onClose()}> + + +
+ + Confirm Transaction Data +
+
+ +
+ {/* Wallet connection status */} + {!activeWalletProvider && ( + + + + No wallet connected. Please connect a wallet before proceeding + with the transaction. + + + )} + + {/* Transaction details */} +
+
+
Amount:
+
+ {confirmationData.depositAmount} BTC +
+ +
+ STX Address: +
+
+ {confirmationData.stxAddress} +
+ +
+ OP_RETURN: +
+
+
+ {confirmationData.opReturnHex} +
+ +
+
+
+ + {/* Wallet provider info */} +
+

Wallet Provider

+
+
+ {activeWalletProvider + ? activeWalletProvider.charAt(0).toUpperCase() + + activeWalletProvider.slice(1) + : "Not Connected"} +
+
+
+ + {/* Fee selection */} +
+

Select priority

+ +
+ setFeePriority(TransactionPriority.Low as any)} + > + +

Low

+

+ {feeEstimates.low.fee} sats +

+

+ ({feeEstimates.low.rate} sat/vB) +

+

30 min

+
+
+ + + setFeePriority(TransactionPriority.Medium as any) + } + > + +

Medium

+

+ {feeEstimates.medium.fee} sats +

+

+ ({feeEstimates.medium.rate} sat/vB) +

+

~20 min

+
+
+ + setFeePriority(TransactionPriority.High as any)} + > + +

High

+

+ {feeEstimates.high.fee} sats +

+

+ ({feeEstimates.high.rate} sat/vB) +

+

~10 min

+
+
+
+ +

+ Fees are estimated based on current network conditions. +

+
+
+ + + + + +
+
+ ); +} From 167ae93c73ea4358f07f74928c42d28ecab7170f Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 17 Apr 2025 13:57:22 +0545 Subject: [PATCH 06/29] fix: mock btc-deposit almost confirmed --- src/app/deposit/page.tsx | 5 +- src/components/btc-deposit/DepositForm.tsx | 670 ++++++++++-------- .../btc-deposit/TransactionConfirmation.tsx | 57 +- src/components/btc-deposit/index.tsx | 176 +++++ 4 files changed, 593 insertions(+), 315 deletions(-) create mode 100644 src/components/btc-deposit/index.tsx diff --git a/src/app/deposit/page.tsx b/src/app/deposit/page.tsx index a04f4fd3..39e58c8d 100644 --- a/src/app/deposit/page.tsx +++ b/src/app/deposit/page.tsx @@ -1,8 +1,9 @@ -import { DepositForm } from "@/components/btc-deposit/DepositForm"; +import BitcoinDeposit from "@/components/btc-deposit"; + const page = () => { return (
- +
); }; diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index 84ed9945..d2cf8342 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -1,56 +1,66 @@ "use client"; -import type React from "react"; - -import { useState } from "react"; -import { Bitcoin, Info, Copy, Check } from "lucide-react"; +import { useState, type ChangeEvent, useEffect } from "react"; +import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; +import { styxSDK } from "@faktoryfun/styx-sdk"; +import { MIN_DEPOSIT_SATS, MAX_DEPOSIT_SATS } from "@faktoryfun/styx-sdk"; import { useToast } from "@/hooks/use-toast"; +import { Bitcoin, Loader2 } from "lucide-react"; +import AuthButton from "@/components/home/auth-button"; +import { useSessionStore } from "@/store/session"; + import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, - CardDescription, -} from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; -import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; -import { styxSDK, StyxSDK } from "@faktoryfun/styx-sdk"; -export enum TransactionPriority { - Low = "low", - Medium = "medium", - High = "high", +interface DepositFormProps { + btcUsdPrice: number | null; + poolStatus: any; + setConfirmationData: (data: ConfirmationData) => void; + setShowConfirmation: (show: boolean) => void; } -export type ConfirmationData = { +export interface ConfirmationData { depositAmount: string; depositAddress: string; stxAddress: string; opReturnHex: string; -}; +} -export function DepositForm() { +export default function DepositForm({ + btcUsdPrice, + poolStatus, + setConfirmationData, + setShowConfirmation, +}: DepositFormProps) { const [amount, setAmount] = useState("0.0001"); const [selectedPreset, setSelectedPreset] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [showConfirmation, setShowConfirmation] = useState(false); - const [confirmationData, setConfirmationData] = - useState(null); - const [copiedField, setCopiedField] = useState(null); + const [maintenanceMode, setMaintenanceMode] = useState(false); const { toast } = useToast(); - // Mock BTC price for USD conversion - const btcUsdPrice = 65000; + // Get session state from Zustand store + const { accessToken, userId, isLoading, initialize } = useSessionStore(); + + // Initialize session on component mount + useEffect(() => { + initialize(); + }, [initialize]); + + const [btcBalance, setBtcBalance] = useState(0.05); + const [activeWalletProvider, setActiveWalletProvider] = useState< + "leather" | "xverse" | null + >(null); + + // Get addresses from the lib - only if we have a session + const userAddress = accessToken ? getStacksAddress() : null; + const btcAddress = accessToken ? getBitcoinAddress() : null; - // Helper functions const formatUsdValue = (amount: number): string => { if (!amount || amount <= 0) return "$0.00"; return new Intl.NumberFormat("en-US", { @@ -62,7 +72,7 @@ export function DepositForm() { }; const calculateUsdValue = (btcAmount: string): number => { - if (!btcAmount) return 0; + if (!btcAmount || !btcUsdPrice) return 0; const numAmount = Number.parseFloat(btcAmount); return isNaN(numAmount) ? 0 : numAmount * btcUsdPrice; }; @@ -70,11 +80,12 @@ export function DepositForm() { const calculateFee = (btcAmount: string): string => { if (!btcAmount || Number.parseFloat(btcAmount) <= 0) return "0.00000000"; const numAmount = Number.parseFloat(btcAmount); - if (isNaN(numAmount)) return "0.00000600"; + if (isNaN(numAmount)) return "0.00003000"; + return numAmount <= 0.002 ? "0.00003000" : "0.00006000"; }; - const handleAmountChange = (e: React.ChangeEvent) => { + const handleAmountChange = (e: ChangeEvent): void => { const value = e.target.value; if (value === "" || /^\d*\.?\d*$/.test(value)) { setAmount(value); @@ -82,288 +93,309 @@ export function DepositForm() { } }; - const handlePresetClick = (presetAmount: string) => { + const handlePresetClick = (presetAmount: string): void => { setAmount(presetAmount); setSelectedPreset(presetAmount); }; - const handleMaxClick = () => { - // In a real implementation, this would calculate the max amount based on the user's BTC balance - // For now, we'll just set a mock value - const maxAmount = "0.05"; - setAmount(maxAmount); - setSelectedPreset("max"); - }; - - const handleCopyToClipboard = (text: string, field: string) => { - navigator.clipboard.writeText(text).then( - () => { - setCopiedField(field); - setTimeout(() => setCopiedField(null), 2000); - }, - (err) => { - console.error("Could not copy text: ", err); + const handleMaxClick = async (): Promise => { + if (btcBalance !== null) { + try { + const feeRates = await styxSDK.getFeeEstimates(); + const selectedRate = feeRates.medium; + const estimatedSize = 1 * 70 + 2 * 33 + 12; + const networkFeeSats = estimatedSize * selectedRate; + const networkFee = networkFeeSats / 100000000; + const maxAmount = Math.max(0, btcBalance - networkFee); + const formattedMaxAmount = maxAmount.toFixed(8); + + setAmount(formattedMaxAmount); + setSelectedPreset("max"); + } catch (error) { + console.error("Error calculating max amount:", error); + const networkFee = 0.000006; + const maxAmount = Math.max(0, btcBalance - networkFee); + setAmount(maxAmount.toFixed(8)); + setSelectedPreset("max"); } - ); + } }; - const prepareTransaction = async () => { - try { - setIsLoading(true); + const handleDepositConfirm = async (): Promise => { + if (maintenanceMode) { + toast({ + title: "Scheduled Maintenance", + description: + "We know you're eager to test this feature! We're working diligently to implement support for both legacy and segwit addresses ahead of schedule. Deposits will be back online in just a few hours. Thank you for your patience.", + variant: "destructive", + }); + return; + } - // Get user addresses - const userAddress = getStacksAddress(); - const btcAddress = getBitcoinAddress(); + if (!amount || Number.parseFloat(amount) <= 0) { + toast({ + title: "Invalid amount", + description: "Please enter a valid BTC amount greater than 0", + variant: "destructive", + }); + return; + } - if (!userAddress || !btcAddress) { - throw new Error("Wallet not connected or addresses not found"); + if (!accessToken || !userAddress) { + toast({ + title: "Not connected", + description: "Please connect your wallet first", + variant: "destructive", + }); + return; + } + + try { + if (!btcAddress) { + throw new Error("No Bitcoin address found in your wallet"); } - // This would be your actual SDK call - const transactionData = await styxSDK.prepareTransaction({ - amount: amount, // BTC amount as string - userAddress: userAddress, // STX address - btcAddress: btcAddress, // BTC address - feePriority: TransactionPriority.Medium, - walletProvider: "xverse", // "leather" or "xverse" - }); + const amountInSats = Math.round(Number.parseFloat(amount) * 100000000); + + console.log(MIN_DEPOSIT_SATS, MAX_DEPOSIT_SATS); + if (amountInSats < MIN_DEPOSIT_SATS) { + toast({ + title: "Minimum deposit required", + description: `Please deposit at least ${ + MIN_DEPOSIT_SATS / 100000000 + } BTC`, + variant: "destructive", + }); + return; + } - console.log("Transaction prepared:", transactionData); + if (amountInSats > MAX_DEPOSIT_SATS) { + toast({ + title: "Beta limitation", + description: `During beta, the maximum deposit amount is ${ + MAX_DEPOSIT_SATS / 100000000 + } BTC. Thank you for your understanding.`, + variant: "destructive", + }); + return; + } - // Set confirmation data - setConfirmationData({ - depositAmount: amount, - depositAddress: transactionData.depositAddress, - stxAddress: userAddress, - opReturnHex: transactionData.opReturnData, - }); + if (poolStatus && amountInSats > poolStatus.estimatedAvailable) { + toast({ + title: "Insufficient liquidity", + description: `The pool currently has ${ + poolStatus.estimatedAvailable / 100000000 + } BTC available. Please try a smaller amount.`, + variant: "destructive", + }); + return; + } - // Show confirmation screen - setShowConfirmation(true); + const amountInBTC = Number.parseFloat(amount); + const networkFeeInBTC = 0.000006; + const totalRequiredBTC = amountInBTC + networkFeeInBTC; + + if ((btcBalance || 0) < totalRequiredBTC) { + const shortfallBTC = totalRequiredBTC - (btcBalance || 0); + throw new Error( + `Insufficient funds. You need ${shortfallBTC.toFixed( + 8 + )} BTC more to complete this transaction.` + ); + } + + try { + console.log("Preparing transaction with SDK..."); + + const transactionData = await styxSDK.prepareTransaction({ + amount, + userAddress, + btcAddress, + feePriority: "medium", + walletProvider: activeWalletProvider, + }); + + console.log("Transaction prepared:", transactionData); + + setConfirmationData({ + depositAmount: amount, + depositAddress: transactionData.depositAddress, + stxAddress: userAddress, + opReturnHex: transactionData.opReturnData, + }); + + setShowConfirmation(true); + } catch (err: any) { + console.error("Error preparing transaction:", err); + + if (isInscriptionError(err)) { + handleInscriptionError(err); + } else if (isUtxoCountError(err)) { + handleUtxoCountError(err); + } else if (isAddressTypeError(err)) { + handleAddressTypeError(err, activeWalletProvider); + } else { + toast({ + title: "Error", + description: + err.message || "Failed to prepare transaction. Please try again.", + variant: "destructive", + }); + } + } + } catch (err: any) { + console.error("Error preparing Bitcoin transaction:", err); - return transactionData; - } catch (error) { - console.error("Error preparing transaction:", error); toast({ title: "Error", description: - error instanceof Error - ? error.message - : "Failed to prepare transaction", + err.message || + "Failed to prepare Bitcoin transaction. Please try again.", variant: "destructive", }); - } finally { - setIsLoading(false); } }; - const handleBackToDeposit = () => { - setShowConfirmation(false); - setConfirmationData(null); - }; + // Helper functions for error handling + function isAddressTypeError(error: Error): boolean { + return ( + error.message.includes("inputType: sh without redeemScript") || + error.message.includes("P2SH") || + error.message.includes("redeem script") + ); + } - const presetAmounts = ["0.001", "0.005", "0.01"]; - const presetLabels = ["0.001 BTC", "0.005 BTC", "0.01 BTC"]; + function handleAddressTypeError( + error: Error, + walletProvider: "leather" | "xverse" | null + ): void { + if (walletProvider === "leather") { + toast({ + title: "Unsupported Address Type", + description: + "Leather wallet does not support P2SH addresses (starting with '3'). Please use a SegWit address (starting with 'bc1') instead.", + variant: "destructive", + }); + } else if (walletProvider === "xverse") { + toast({ + title: "P2SH Address Error", + description: + "There was an issue with the P2SH address. This might be due to wallet limitations. Try using a SegWit address (starting with 'bc1') instead.", + variant: "destructive", + }); + } else { + toast({ + title: "P2SH Address Not Supported", + description: + "Your wallet doesn't provide the necessary information for your P2SH address. Please try using a SegWit address (starting with bc1) instead.", + variant: "destructive", + }); + } + } - if (showConfirmation && confirmationData) { - return ( - - - - - Deposit Confirmation - - - Please review your deposit details below - - - -
-
- Amount: - - {confirmationData.depositAmount} BTC - -
-
- USD Value: - - {formatUsdValue( - calculateUsdValue(confirmationData.depositAmount) - )} - -
-
- - STX Address: - -
- - {confirmationData.stxAddress} - - -
-
-
+ function isInscriptionError(error: Error): boolean { + return error.message.includes("with inscriptions"); + } -
-
- Deposit Address: - -
-
- {confirmationData.depositAddress} -
-
+ function handleInscriptionError(error: Error): void { + toast({ + title: "Inscriptions Detected", + description: error.message, + variant: "destructive", + }); + } -
-
- OP_RETURN Data: - -
-
- {confirmationData.opReturnHex} -
-
+ function isUtxoCountError(error: Error): boolean { + return error.message.includes("small UTXOs"); + } -
-

- Please send exactly {confirmationData.depositAmount} BTC to the - deposit address above. -

-
-
- - - - -
+ function handleUtxoCountError(error: Error): void { + toast({ + title: "Too Many UTXOs", + description: error.message, + variant: "destructive", + }); + } + + const presetAmounts: string[] = ["0.001", "0.005", "0.01"]; + const presetLabels: string[] = ["0.001 BTC", "0.005 BTC", "0.01 BTC"]; + + // Set wallet provider when authenticated + const handleWalletSelect = (walletType: "leather" | "xverse") => { + setActiveWalletProvider(walletType); + }; + + // Render loading state while initializing session + if (isLoading) { + return ( +
+ +

Loading your session...

+
); } return ( - - - - - Bitcoin Deposit - - - -
-
-
- - Bitcoin -
- - {formatUsdValue(calculateUsdValue(amount))} - +
+ {/* From: Bitcoin */} +
+
+
+ + Bitcoin
+ + {formatUsdValue(calculateUsdValue(amount))} + +
-
- - - BTC - -
+
+ + + BTC + +
-
- {presetAmounts.map((presetAmount, index) => ( - - ))} + {/* Preset amounts */} +
+ {presetAmounts.map((presetAmount, index) => ( -
+ ))} +
+
-
+ {/* Fee Information Box */} + +
Estimated time @@ -372,53 +404,71 @@ export function DepositForm() { 1 Block ~ 10 min
-
+
Service fee {amount && Number.parseFloat(amount) > 0 && btcUsdPrice - ? `${formatUsdValue( + ? formatUsdValue( Number.parseFloat(calculateFee(amount)) * btcUsdPrice - )} ~ ${calculateFee(amount)} BTC` - : "$0.00 ~ 0.00000000 BTC"} - -
-
- - Pool liquidity - - - $650,000 ~ 10.00000000 BTC + ) + : "$0.00"}{" "} + ~ {calculateFee(amount)} BTC
-
- - - -
- How it works - -
+ {/* Add Pool Liquidity information */} + {poolStatus && ( +
+ + Pool liquidity + + + {formatUsdValue( + (poolStatus.estimatedAvailable / 100000000) * + (btcUsdPrice || 0) + )}{" "} + ~ {(poolStatus.estimatedAvailable / 100000000).toFixed(8)} BTC + +
+ )} +
+
+ + {/* Accordion with Additional Info */} + + +
+ + How it works - -

- Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin state - reading. No intermediaries or multi-signature scheme needed. - Trustless. Fast. Secure. -

-
- - - - +
+ + Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin state + reading. No intermediaries or multi-signature scheme needed. + Trustless. Fast. Secure. + +
+
+ + {/* Action Button */} + {!accessToken ? ( +
+

+ Connect your wallet to continue +

+
+ +
+
+ ) : ( - - + )} +
); } diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index 22b490df..b98711bb 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -1,12 +1,13 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { hex } from "@scure/base"; import * as btc from "@scure/btc-signer"; import { styxSDK, TransactionPriority } from "@faktoryfun/styx-sdk"; import { request as xverseRequest } from "sats-connect"; import { useToast } from "@/hooks/use-toast"; -import { ArrowLeft, Copy, Check, AlertTriangle } from "lucide-react"; +import { ArrowLeft, Copy, Check, AlertTriangle, Loader2 } from "lucide-react"; +import { useSessionStore } from "@/store/session"; import { Dialog, @@ -69,6 +70,14 @@ export default function TransactionConfirmation({ const [currentDepositId, setCurrentDepositId] = useState(null); const [copied, setCopied] = useState>({}); + // Get session state from Zustand store + const { accessToken, userId, isLoading, initialize } = useSessionStore(); + + // Initialize session on component mount + useEffect(() => { + initialize(); + }, [initialize]); + const [feeEstimates, setFeeEstimates] = useState({ low: { rate: 1, fee: 0, time: "30 min" }, medium: { rate: 3, fee: 0, time: "~20 min" }, @@ -108,6 +117,16 @@ export default function TransactionConfirmation({ activeWalletProvider ); + // Check if user is authenticated + if (!accessToken) { + toast({ + title: "Authentication required", + description: "Please sign in before proceeding with the transaction", + variant: "destructive", + }); + return; + } + // Check if wallet is connected if (!activeWalletProvider) { toast({ @@ -610,6 +629,22 @@ export default function TransactionConfirmation({ return `${address.slice(0, 8)}...${address.slice(-8)}`; }; + // Render loading state while initializing session + if (isLoading) { + return ( + !open && onClose()}> + +
+ +

+ Loading your session... +

+
+
+
+ ); + } + return ( !open && onClose()}> @@ -628,6 +663,20 @@ export default function TransactionConfirmation({
+ {/* Authentication status */} + {!accessToken && ( + + + + Authentication required. Please sign in before proceeding with + the transaction. + + + )} + {/* Wallet connection status */} {!activeWalletProvider && ( {btcTxStatus === "pending" ? "Processing..." : "Proceed to Wallet"} diff --git a/src/components/btc-deposit/index.tsx b/src/components/btc-deposit/index.tsx new file mode 100644 index 00000000..6c4bb557 --- /dev/null +++ b/src/components/btc-deposit/index.tsx @@ -0,0 +1,176 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { TransactionPriority } from "@faktoryfun/styx-sdk"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card } from "@/components/ui/card"; +import DepositForm, { type ConfirmationData } from "./DepositForm"; +import TransactionConfirmation from "./TransactionConfirmation"; +import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; +import { useSessionStore } from "@/store/session"; +import AuthButton from "@/components/home/auth-button"; + +// These would be replaced with actual hooks in a real implementation +const useFormattedBtcPrice = () => { + return { price: 65000 }; +}; + +const useSdkPoolStatus = () => { + return { + data: { + estimatedAvailable: 500000000, // 5 BTC in satoshis + }, + isLoading: false, + }; +}; + +const useSdkDepositHistory = (userAddress: string | null) => { + return { + data: [], + isLoading: false, + }; +}; + +const useSdkAllDepositsHistory = () => { + return { + data: [], + isLoading: false, + }; +}; + +// Placeholder components for history tabs +const MyHistory = ({ depositHistory, isLoading, btcUsdPrice }: any) => ( +
+

Your deposit history will appear here

+
+); + +const AllDeposits = ({ allDepositsHistory, isLoading, btcUsdPrice }: any) => ( +
+

All deposits history will appear here

+
+); + +export default function BitcoinDeposit() { + // Get session state from Zustand store + const { accessToken, userId, isLoading } = useSessionStore(); + + // State management + const [activeTab, setActiveTab] = useState("deposit"); + const [showConfirmation, setShowConfirmation] = useState(false); + const [confirmationData, setConfirmationData] = + useState(null); + const [feePriority, setFeePriority] = useState( + TransactionPriority.Medium + ); + const [activeWalletProvider, setActiveWalletProvider] = useState< + "leather" | "xverse" | null + >("xverse"); + + useEffect(() => { + // When accessToken becomes available, set the wallet provider to Xverse + if (accessToken) { + setActiveWalletProvider("xverse"); + } + }, [accessToken]); + + // Get addresses directly + const userAddress = accessToken ? getStacksAddress() : null; + const btcAddress = accessToken ? getBitcoinAddress() : null; + + // Data fetching hooks + const { price: btcUsdPrice } = useFormattedBtcPrice(); + const { data: poolStatus } = useSdkPoolStatus(); + const { data: depositHistory, isLoading: isDepositHistoryLoading } = + useSdkDepositHistory(userAddress); + const { data: allDepositsHistory, isLoading: isAllDepositsHistoryLoading } = + useSdkAllDepositsHistory(); + + // Determine if we should show the auth prompt + // If we have a userAddress, we're connected + // const isConnected = !!userAddress; + + // Render authentication prompt if not connected + if (!accessToken) { + return ( +
+
+

+ Deposit BTC in just 1 Bitcoin block +

+

+ Fast, secure, and trustless +

+
+ + +

+ Please connect your wallet to access the deposit feature +

+ +
+
+ ); + } + + return ( +
+
+

+ Deposit BTC in just 1 Bitcoin block +

+

+ Fast, secure, and trustless +

+
+ + + + + Deposit + My History + All Deposits + + + + + + + + + + + + + + + + + {showConfirmation && confirmationData && ( + setShowConfirmation(false)} + feePriority={feePriority} + setFeePriority={setFeePriority} + userAddress={userAddress || ""} + btcAddress={btcAddress || ""} + activeWalletProvider={activeWalletProvider} + /> + )} +
+ ); +} From f932492c52f3a95577f3f804675e1cfcb8eb6784 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Mon, 28 Apr 2025 12:43:33 +0545 Subject: [PATCH 07/29] fix: remove static data and use sdk for price and history --- src/components/btc-deposit/DepositForm.tsx | 2 +- src/components/btc-deposit/index.tsx | 117 ++++++++++++--------- 2 files changed, 71 insertions(+), 48 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index d2cf8342..8e00657c 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -463,7 +463,7 @@ export default function DepositForm({ ) : (
+ {/* Display user's BTC balance */} + {accessToken && ( +
+ + Balance:{" "} + {isBalanceLoading + ? "Loading..." + : btcBalance !== null && btcBalance !== undefined + ? `${btcBalance.toFixed(8)} BTC${ + btcUsdPrice + ? ` (${formatUsdValue(btcBalance * (btcUsdPrice || 0))})` + : "" + }` + : "Unable to load balance"} + +
+ )} + {/* Preset amounts */}
{presetAmounts.map((presetAmount, index) => ( @@ -386,6 +430,7 @@ export default function DepositForm({ : "" } onClick={handleMaxClick} + disabled={btcBalance === null || btcBalance === undefined} > MAX From 9c05c3445fc77f2349c4fdce9742d1bf683b5d52 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 30 Apr 2025 11:19:31 +0545 Subject: [PATCH 11/29] dep: add btc-signer --- package-lock.json | 85 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 86 insertions(+) diff --git a/package-lock.json b/package-lock.json index a5ba6975..f0f6ae48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", + "@scure/btc-signer": "^1.8.0", "@stacks/connect": "^7.10.0", "@stacks/connect-react": "^22.6.0", "@stacks/connect-ui": "^6.4.1", @@ -865,6 +866,33 @@ "node": ">= 10" } }, + "node_modules/@noble/curves": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz", + "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { "version": "1.1.5", "funding": [ @@ -2081,6 +2109,42 @@ "@scure/base": "~1.1.0" } }, + "node_modules/@scure/btc-signer": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@scure/btc-signer/-/btc-signer-1.8.0.tgz", + "integrity": "sha512-lzf9ugp2hZwP84bdRQuxdX2iib3wyUs7+8+Ph/hanVaXWGOZfSfgEZFaOyocj/Qh0Igt1WHkZh6hdh4KloynNQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5", + "micro-packed": "~0.7.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/btc-signer/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/btc-signer/node_modules/@scure/base": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.5.tgz", + "integrity": "sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sinclair/typebox": { "version": "0.25.24", "dev": true, @@ -7926,6 +7990,27 @@ "node": ">= 8.0.0" } }, + "node_modules/micro-packed": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", + "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", + "license": "MIT", + "dependencies": { + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed/node_modules/@scure/base": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.5.tgz", + "integrity": "sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/micro/node_modules/arg": { "version": "4.1.0", "dev": true, diff --git a/package.json b/package.json index 2ebd0910..1e1e5fb4 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", + "@scure/btc-signer": "^1.8.0", "@stacks/connect": "^7.10.0", "@stacks/connect-react": "^22.6.0", "@stacks/connect-ui": "^6.4.1", From 60d4eec1b9799a6be2bbdae91aac8e66cbb31c60 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 30 Apr 2025 11:20:50 +0545 Subject: [PATCH 12/29] update(dep): styx/sdk --- package-lock.json | 13 +++++-------- package.json | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0f6ae48..57a62dff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.0.8", + "@faktoryfun/styx-sdk": "^1.0.14", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", @@ -380,13 +380,10 @@ "license": "MIT" }, "node_modules/@faktoryfun/styx-sdk": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.0.8.tgz", - "integrity": "sha512-VAKrMeuJ80yeFdv4HAC9iUCRNZTI7W62KogL5LM1AlLaEcB6ntfy8eWbrtB7vHiEp9amkmjDNHjSZXJSME3dHw==", - "license": "MIT", - "dependencies": { - "axios": "^1.6.2" - } + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.0.14.tgz", + "integrity": "sha512-dVG304nHJglLYn1QeNKOmA14v5C00l+1y225DUE9hjzODnG3z1/1zdiLfZBzc1lGgrGALvDvEqI+HNCfpP5ELg==", + "license": "MIT" }, "node_modules/@fastify/busboy": { "version": "2.1.1", diff --git a/package.json b/package.json index 1e1e5fb4..1503cb5d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.0.8", + "@faktoryfun/styx-sdk": "^1.0.14", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", From d8d04b4d27208cc12013753bc8f92fc37df92d45 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 30 Apr 2025 11:37:20 +0545 Subject: [PATCH 13/29] fix: use consistent brand color --- src/components/btc-deposit/DepositForm.tsx | 4 +- .../btc-deposit/TransactionConfirmation.tsx | 118 +++++++----------- 2 files changed, 47 insertions(+), 75 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index b17832f8..f29dfc66 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -379,7 +379,7 @@ export default function DepositForm({ value={amount} onChange={handleAmountChange} placeholder="0.00000000" - className="text-right pr-16 pl-16 h-[60px] text-xl bg-secondary" + className="text-right pr-16 pl-16 h-[60px] text-xl" /> BTC @@ -438,7 +438,7 @@ export default function DepositForm({
{/* Fee Information Box */} - +
diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index 6696d0bd..b6820986 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -8,6 +8,7 @@ import { request as xverseRequest } from "sats-connect"; import { useToast } from "@/hooks/use-toast"; import { ArrowLeft, Copy, Check, AlertTriangle, Loader2 } from "lucide-react"; import { useSessionStore } from "@/store/session"; +import { useClipboard } from "@/helpers/clipboard-utils"; import { Dialog, @@ -68,7 +69,7 @@ export default function TransactionConfirmation({ >("idle"); const [btcTxId, setBtcTxId] = useState(""); const [currentDepositId, setCurrentDepositId] = useState(null); - const [copied, setCopied] = useState>({}); + const { copiedText, copyToClipboard } = useClipboard(); // Get session state from Zustand store const { accessToken, userId, isLoading, initialize } = useSessionStore(); @@ -88,29 +89,6 @@ export default function TransactionConfirmation({ return address.startsWith("3"); }; - const copyToClipboard = async (text: string, field: string) => { - try { - await navigator.clipboard.writeText(text); - setCopied({ ...copied, [field]: true }); - - toast({ - title: "Copied", - description: "The information has been copied to your clipboard", - }); - - setTimeout(() => { - setCopied({ ...copied, [field]: false }); - }, 2000); - } catch (err) { - console.error("Failed to copy text: ", err); - toast({ - title: "Failed to copy", - description: "Please try again or copy manually", - variant: "destructive", - }); - } - }; - const executeBitcoinTransaction = async (): Promise => { console.log( "Starting transaction with activeWalletProvider:", @@ -628,12 +606,10 @@ export default function TransactionConfirmation({ if (isLoading) { return ( !open && onClose()}> - +
- -

- Loading your session... -

+ +

Loading your session...

@@ -642,7 +618,7 @@ export default function TransactionConfirmation({ return ( !open && onClose()}> - +
{/* Wallet provider info */} -
+

Wallet Provider

-
+
{activeWalletProvider ? activeWalletProvider.charAt(0).toUpperCase() + activeWalletProvider.slice(1) @@ -740,33 +712,33 @@ export default function TransactionConfirmation({
{/* Fee selection */} -
+

Select priority

setFeePriority(TransactionPriority.Low as any)} >

Low

-

+

{feeEstimates.low.fee} sats

-

+

({feeEstimates.low.rate} sat/vB)

-

30 min

+

30 min

setFeePriority(TransactionPriority.Medium as any) @@ -774,37 +746,37 @@ export default function TransactionConfirmation({ >

Medium

-

+

{feeEstimates.medium.fee} sats

-

+

({feeEstimates.medium.rate} sat/vB)

-

~20 min

+

~20 min

setFeePriority(TransactionPriority.High as any)} >

High

-

+

{feeEstimates.high.fee} sats

-

+

({feeEstimates.high.rate} sat/vB)

-

~10 min

+

~10 min

-

+

Fees are estimated based on current network conditions.

@@ -814,12 +786,12 @@ export default function TransactionConfirmation({
- - {isDataLoading ? ( -
- -

- Loading deposit data... -

-
- ) : btcPriceError ? ( -
-

- Error loading BTC price data. Please try again later. -

-
- ) : ( - + + Deposit + My History + + + + + {isDataLoading ? ( +
+ +

+ Loading deposit data... +

+
+ ) : btcPriceError ? ( +
+

+ Error loading BTC price data. Please try again later. +

+
+ ) : ( + + )} +
+
+ + + - )} -
+ + {showConfirmation && confirmationData && ( { + if (amount === null || amount === undefined) return "0.00000000"; + return amount.toFixed(8); + }; + + // Format USD value + const formatUsdValue = (amount: number): string => { + if (!amount || amount <= 0) return "$0.00"; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(amount); + }; + + // Get status badge variant + const getStatusVariant = ( + status: string + ): "default" | "secondary" | "destructive" | "outline" => { + switch (status) { + case "broadcast": + return "secondary"; + case "processing": + return "default"; + case "confirmed": + return "outline"; + default: + return "destructive"; + } + }; + + // Get truncated tx id for display + const getTruncatedTxId = (txId: string | null): string => { + if (!txId) return "N/A"; + return `${txId.substring(0, 6)}...${txId.substring(txId.length - 4)}`; + }; + + // Format time using date-fns with timestamp (number) handling + const formatTimeAgo = (timestamp: number | null): string => { + if (timestamp === null || timestamp === undefined) return "Unknown"; + + try { + // Convert timestamp to Date object + const date = new Date(timestamp); + + // Check if date is valid + if (isNaN(date.getTime())) { + return "Invalid date"; + } + + return formatDistanceToNow(date, { addSuffix: true }); + } catch (error) { + console.error("Error formatting date:", error); + return "Invalid date"; + } + }; + + return ( +
+ {!accessToken ? ( + +

+ Connect your wallet to view your deposit history +

+ +
+ ) : isLoading ? ( +
+ +

+ Loading deposit history... +

+
+ ) : depositHistory && depositHistory.length > 0 ? ( + + {/* Add refetching overlay */} + {isRefetching && ( +
+
+ +

Updating history...

+
+
+ )} +
+ + + + Time + Amount + Status + Tx ID + + + + {depositHistory.map((deposit: Deposit) => ( + + + {formatTimeAgo(deposit.createdAt)} + + +
+ {formatBtcAmount(deposit.btcAmount)} +
+
+ {formatUsdValue(deposit.btcAmount * (btcUsdPrice || 0))} +
+
+ + + {deposit.status} + + + + {deposit.btcTxId ? ( + + {getTruncatedTxId(deposit.btcTxId)} + + + ) : ( + + N/A + + )} + +
+ ))} +
+
+
+
+ ) : ( + +

No deposit history found

+

+ Make your first deposit to see it here +

+
+ )} +
+ ); +} From 3acb11005699b08a560ad1311b68bd1532493d1d Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 30 Apr 2025 11:59:59 +0545 Subject: [PATCH 15/29] feat: add all deposit --- src/components/btc-deposit/all-deposits.tsx | 264 ++++++++++++++++++++ src/components/btc-deposit/index.tsx | 23 +- 2 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 src/components/btc-deposit/all-deposits.tsx diff --git a/src/components/btc-deposit/all-deposits.tsx b/src/components/btc-deposit/all-deposits.tsx new file mode 100644 index 00000000..fa21fcb3 --- /dev/null +++ b/src/components/btc-deposit/all-deposits.tsx @@ -0,0 +1,264 @@ +"use client"; + +import { ExternalLink, Loader2 } from "lucide-react"; +import type { Deposit } from "@faktoryfun/styx-sdk"; +import { formatDistanceToNow } from "date-fns"; +import { Card } from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import useResolveBnsOrAddress from "@/hooks/deposit/useResolveBnsOrAddress"; + +interface AllDepositsProps { + allDepositsHistory: Deposit[] | undefined; + isLoading: boolean; + btcUsdPrice: number | null | undefined; + isRefetching?: boolean; +} + +// AddressCell Component +const AddressCell = ({ address }: { address: string }) => { + const { data } = useResolveBnsOrAddress(address); + + const displayAddress = data?.resolvedValue + ? data.resolvedValue + : formatAddress(address); + + const bgColor = getBackgroundColor(address); + + const handleAddressClick = () => { + window.open( + `https://explorer.hiro.so/address/${address}?chain=mainnet`, + "_blank" + ); + }; + + return ( +
+ {displayAddress} +
+ ); +}; + +// Helper functions +const formatAddress = (address: string): string => { + if (!address) return "Unknown"; + if (address.length <= 10) return address; + return `${address.substring(0, 5)}...${address.substring( + address.length - 5 + )}`; +}; + +const getBackgroundColor = (address: string): string => { + if (!address) return "#3f3f46"; // zinc-700 + + // Simple hash function to generate a color + let hash = 0; + for (let i = 0; i < address.length; i++) { + hash = address.charCodeAt(i) + ((hash << 5) - hash); + } + + // Generate HSL color with fixed saturation and lightness + const h = Math.abs(hash % 360); + return `hsl(${h}, 70%, 30%)`; +}; + +export default function AllDeposits({ + allDepositsHistory, + isLoading, + btcUsdPrice, + isRefetching = false, +}: AllDepositsProps) { + // Format BTC amount for display + const formatBtcAmount = (amount: number | null): string => { + if (amount === null || amount === undefined) return "0.00000000"; + return amount.toFixed(8); + }; + + // Format USD value + const formatUsdValue = (amount: number): string => { + if (!amount || amount <= 0) return "$0.00"; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(amount); + }; + + // Get status badge variant + const getStatusVariant = ( + status: string + ): "default" | "secondary" | "destructive" | "outline" => { + switch (status) { + case "broadcast": + return "secondary"; + case "processing": + return "default"; + case "confirmed": + return "outline"; + default: + return "destructive"; + } + }; + + // Get truncated tx id for display + const getTruncatedTxId = (txId: string | null): string => { + if (!txId) return "N/A"; + return `${txId.substring(0, 6)}...${txId.substring(txId.length - 4)}`; + }; + + // Format time using date-fns with timestamp (number) handling + const formatTimeAgo = (timestamp: number | null): string => { + if (timestamp === null || timestamp === undefined) return "Unknown"; + + try { + // Convert timestamp to Date object + const date = new Date(timestamp); + + // Check if date is valid + if (isNaN(date.getTime())) { + return "Invalid date"; + } + + return formatDistanceToNow(date, { addSuffix: true }); + } catch (error) { + console.error("Error formatting date:", error); + return "Invalid date"; + } + }; + + // Calculate stats + const totalDeposits = allDepositsHistory?.length || 0; + const totalVolume = + allDepositsHistory?.reduce((sum, deposit) => sum + deposit.btcAmount, 0) || + 0; + const uniqueUsers = allDepositsHistory + ? new Set(allDepositsHistory.map((deposit) => deposit.stxReceiver)).size + : 0; + + return ( +
+

Recent Network Activity

+ + {/* Stats summary box */} + {!isLoading && allDepositsHistory && allDepositsHistory.length > 0 && ( + +
+
+

Total Deposits

+

{totalDeposits}

+
+
+

Total Volume

+

+ {formatBtcAmount(totalVolume)} +

+

BTC

+
+
+

Unique Users

+

{uniqueUsers}

+
+
+
+ )} + + {isLoading ? ( +
+ +

+ Loading network activity... +

+
+ ) : allDepositsHistory && allDepositsHistory.length > 0 ? ( + + {/* Add refetching overlay */} + {isRefetching && ( +
+
+ +

Updating data...

+
+
+ )} +
+ + + + Time + Amount + Status + User + Tx ID + + + + {allDepositsHistory.map((deposit: Deposit) => ( + + + {formatTimeAgo(deposit.createdAt)} + + +
+ {formatBtcAmount(deposit.btcAmount)} +
+
+ {formatUsdValue(deposit.btcAmount * (btcUsdPrice || 0))} +
+
+ + + {deposit.status} + + + + + + + {deposit.btcTxId ? ( + + {getTruncatedTxId(deposit.btcTxId)} + + + ) : ( + + N/A + + )} + +
+ ))} +
+
+
+
+ ) : ( + +

No network activity found

+

+ Network activity will appear here once deposits are made +

+
+ )} +
+ ); +} diff --git a/src/components/btc-deposit/index.tsx b/src/components/btc-deposit/index.tsx index e143ad1a..2087b5f9 100644 --- a/src/components/btc-deposit/index.tsx +++ b/src/components/btc-deposit/index.tsx @@ -8,12 +8,14 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import DepositForm, { type ConfirmationData } from "./DepositForm"; import TransactionConfirmation from "./TransactionConfirmation"; import MyHistory from "./my-history"; +import AllDeposits from "./all-deposits"; import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; import { useSessionStore } from "@/store/session"; import AuthButton from "@/components/home/auth-button"; import { useFormattedBtcPrice } from "@/hooks/deposit/useSdkBtcPrice"; import useSdkPoolStatus from "@/hooks/deposit/useSdkPoolStatus"; import useSdkDepositHistory from "@/hooks/deposit/useSdkDepositHistory"; +import useSdkAllDepositsHistory from "@/hooks/deposit/useSdkAllDepositsHistory"; export default function BitcoinDeposit() { // Get session state from Zustand store @@ -44,13 +46,20 @@ export default function BitcoinDeposit() { const { data: poolStatus, isLoading: isPoolStatusLoading } = useSdkPoolStatus(); - // Use the provided deposit history hook + // User's deposit history const { data: depositHistory, isLoading: isHistoryLoading, isRefetching: isHistoryRefetching, } = useSdkDepositHistory(userAddress); + // All network deposits - using the provided hook + const { + data: allDepositsHistory, + isLoading: isAllDepositsLoading, + isRefetching: isAllDepositsRefetching, + } = useSdkAllDepositsHistory(); + // Determine if we're still loading critical data const isDataLoading = isBtcPriceLoading || isPoolStatusLoading || btcUsdPrice === undefined; @@ -95,9 +104,10 @@ export default function BitcoinDeposit() {
- + Deposit My History + All Deposits @@ -134,6 +144,15 @@ export default function BitcoinDeposit() { isRefetching={isHistoryRefetching} /> + + + + {showConfirmation && confirmationData && ( From 16e94e7fa12bf6e0da5ed3277209e1af7a47a7d6 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 30 Apr 2025 12:48:35 +0545 Subject: [PATCH 16/29] fix: sBTC->BTC --- src/components/reusables/asset-tracker.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/reusables/asset-tracker.tsx b/src/components/reusables/asset-tracker.tsx index 21e969a1..388fdd1c 100644 --- a/src/components/reusables/asset-tracker.tsx +++ b/src/components/reusables/asset-tracker.tsx @@ -106,7 +106,7 @@ const AssetTracker = () => {
{!isLoaded && (

- Checking your sBTC status... + Checking your BTC status...

)} @@ -115,7 +115,7 @@ const AssetTracker = () => { className="text-center text-primary font-medium cursor-pointer hover:underline" onClick={openDepositModal} > - You have sBTC in your wallet! Click here to deposit it in your smart + You have BTC in your wallet! Click here to deposit it in your smart wallet.

)} @@ -129,13 +129,13 @@ const AssetTracker = () => { > Bitflow or Velar {" "} - to deposite sBTC in your wallet. + to deposite BTC in your wallet.

)} {isLoaded && hasSbtc === null && currentAddress && (

- Unable to check your sBTC status. Visit{" "} + Unable to check your BTC status. Visit{" "} Bitflow {" "} @@ -145,7 +145,7 @@ const AssetTracker = () => { {isLoaded && !currentAddress && (

- No wallet connected. Connect your wallet to check for sBTC. + No wallet connected. Connect your wallet to check for BTC.

)}
@@ -159,13 +159,13 @@ const AssetTracker = () => { > - Deposit sBTC + Deposit BTC Coming soon

Feature Coming Soon

- The ability to deposit sBTC into your smart wallet will be + The ability to deposit BTC into your smart wallet will be available in a future update.

From b211113dbbf83459fade591a2bb8e6f103e6fd63 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Tue, 6 May 2025 11:53:54 +0545 Subject: [PATCH 17/29] fix: update --- src/components/btc-deposit/DepositForm.tsx | 173 ++++++++++++++++-- .../btc-deposit/TransactionConfirmation.tsx | 103 ++++++++++- src/components/btc-deposit/all-deposits.tsx | 59 +++--- 3 files changed, 292 insertions(+), 43 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index f29dfc66..359c689a 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -5,7 +5,7 @@ import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; import { styxSDK } from "@faktoryfun/styx-sdk"; import { MIN_DEPOSIT_SATS, MAX_DEPOSIT_SATS } from "@faktoryfun/styx-sdk"; import { useToast } from "@/hooks/use-toast"; -import { Bitcoin, Loader2 } from "lucide-react"; +import { Bitcoin, Loader2, Zap } from "lucide-react"; import AuthButton from "@/components/home/auth-button"; import { useSessionStore } from "@/store/session"; import { Button } from "@/components/ui/button"; @@ -17,7 +17,10 @@ import { AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; +import { Badge } from "@/components/ui/badge"; import { useQuery } from "@tanstack/react-query"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; interface DepositFormProps { btcUsdPrice: number | null; @@ -31,6 +34,7 @@ export interface ConfirmationData { depositAddress: string; stxAddress: string; opReturnHex: string; + isBlaze?: boolean; } export default function DepositForm({ @@ -39,9 +43,15 @@ export default function DepositForm({ setConfirmationData, setShowConfirmation, }: DepositFormProps) { - const [amount, setAmount] = useState("0.0001"); + const [amount, setAmount] = useState("0.001"); const [selectedPreset, setSelectedPreset] = useState(null); const { toast } = useToast(); + const [useBlazeSubnet, setUseBlazeSubnet] = useState(false); + const [feeEstimates, setFeeEstimates] = useState({ + low: { rate: 1, fee: 0, time: "30 min" }, + medium: { rate: 3, fee: 0, time: "~20 min" }, + high: { rate: 5, fee: 0, time: "~10 min" }, + }); // Get session state from Zustand store const { accessToken, isLoading, initialize } = useSessionStore(); @@ -87,6 +97,66 @@ export default function DepositForm({ refetchOnWindowFocus: false, }); + // Fetch fee estimates from mempool.space + const fetchMempoolFeeEstimates = async () => { + try { + console.log("Fetching fee estimates directly from mempool.space"); + const response = await fetch( + "https://mempool.space/api/v1/fees/recommended" + ); + const data = await response.json(); + + // Log the raw values to help with debugging + console.log("Raw mempool.space fee data:", data); + + // Map to the correct fee estimate fields + const lowRate = data.hourFee; + const mediumRate = data.halfHourFee; + const highRate = data.fastestFee; + + // Don't modify the rates, use them as-is + return { + low: { + rate: lowRate, + fee: Math.round(lowRate * 148), + time: "~1 hour", + }, + medium: { + rate: mediumRate, + fee: Math.round(mediumRate * 148), + time: "~30 min", + }, + high: { + rate: highRate, + fee: Math.round(highRate * 148), + time: "~10 min", + }, + }; + } catch (error) { + console.error("Error fetching fee estimates from mempool.space:", error); + // Fallback to default values that better reflect current network conditions + return { + low: { rate: 3, fee: 444, time: "~1 hour" }, + medium: { rate: 3, fee: 444, time: "~30 min" }, + high: { rate: 5, fee: 740, time: "~10 min" }, + }; + } + }; + + // Fetch fee estimates on component mount + useEffect(() => { + const getFeeEstimates = async () => { + try { + const estimates = await fetchMempoolFeeEstimates(); + setFeeEstimates(estimates); + } catch (error) { + console.error("Error fetching initial fee estimates:", error); + } + }; + + getFeeEstimates(); + }, []); + const formatUsdValue = (amount: number): string => { if (!amount || amount <= 0) return "$0.00"; return new Intl.NumberFormat("en-US", { @@ -178,6 +248,38 @@ export default function DepositForm({ throw new Error("No Bitcoin address found in your wallet"); } + // IMPORTANT: Calculate the total amount including service fee + const userInputAmount = Number.parseFloat(amount); + const serviceFee = Number.parseFloat(calculateFee(amount)); + const totalAmount = (userInputAmount + serviceFee).toFixed(8); + + console.log("Transaction amounts:", { + userInputAmount, + serviceFee, + totalAmount, + }); + + // Always fetch fresh fee estimates before transaction + let currentFeeRates; + try { + console.log( + "Fetching fresh fee estimates before transaction preparation" + ); + const estimatesResult = await fetchMempoolFeeEstimates(); + currentFeeRates = { + low: estimatesResult.low.rate, + medium: estimatesResult.medium.rate, + high: estimatesResult.high.rate, + }; + + // Update the UI fee display + setFeeEstimates(estimatesResult); + console.log("Using fee rates:", currentFeeRates); + } catch (error) { + console.warn("Error fetching fee estimates, using defaults:", error); + currentFeeRates = { low: 1, medium: 3, high: 5 }; + } + const amountInSats = Math.round(Number.parseFloat(amount) * 100000000); console.log(MIN_DEPOSIT_SATS, MAX_DEPOSIT_SATS); @@ -233,20 +335,22 @@ export default function DepositForm({ console.log("Preparing transaction with SDK..."); const transactionData = await styxSDK.prepareTransaction({ - amount, + amount: totalAmount, // Now includes service fee userAddress, btcAddress, feePriority: "medium", walletProvider: activeWalletProvider, + feeRates: currentFeeRates, }); console.log("Transaction prepared:", transactionData); setConfirmationData({ - depositAmount: amount, + depositAmount: totalAmount, depositAddress: transactionData.depositAddress, stxAddress: userAddress, opReturnHex: transactionData.opReturnData, + isBlaze: useBlazeSubnet, }); setShowConfirmation(true); @@ -345,9 +449,10 @@ export default function DepositForm({ const presetAmounts: string[] = ["0.0001", "0.0002"]; const presetLabels: string[] = ["0.0001 BTC", "0.0002 BTC"]; - // Set wallet provider when authenticated - const handleWalletSelect = (walletType: "leather" | "xverse") => { - setActiveWalletProvider(walletType); + // Determine button text based on connection state + const getButtonText = () => { + if (!accessToken) return "Connect Wallet"; + return "Confirm Deposit"; }; // Render loading state while initializing session @@ -438,14 +543,14 @@ export default function DepositForm({
{/* Fee Information Box */} - +
Estimated time - 1 Block ~ 10 min + {feeEstimates.medium.time}
@@ -478,6 +583,46 @@ export default function DepositForm({ + {/* Blaze Fast Subnet Option NOT SURE IF I SHOULD ADD IT BUT KEEPING IT FOR LATER JUST IN CASE */} + {/*
setUseBlazeSubnet(!useBlazeSubnet)} + > +
+
+ + {useBlazeSubnet && ( +
+ )} +
+
+ +

+ Near-instant confirmations with high throughput +

+
+
+
+ + BETA + + {useBlazeSubnet && ( +
+ +
+ )} +
+
*/} + {/* Accordion with Additional Info */} @@ -487,9 +632,11 @@ export default function DepositForm({
- Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin state - reading. No intermediaries or multi-signature scheme needed. - Trustless. Fast. Secure. +

+ Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin state + reading. No intermediaries or multi-signature scheme needed. + Trustless. Fast. Secure. +

@@ -510,7 +657,7 @@ export default function DepositForm({ className="h-[60px] text-xl bg-primary w-full" onClick={handleDepositConfirm} > - Confirm Deposit + {getButtonText()} )}
diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index b6820986..45bb9662 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -39,6 +39,9 @@ interface TransactionConfirmationProps { userAddress: string; btcAddress: string; activeWalletProvider: "leather" | "xverse" | null; + refetchDepositHistory: () => Promise; + refetchAllDeposits: () => Promise; + setIsRefetching: (isRefetching: boolean) => void; } interface XverseSignPsbtResponse { @@ -62,13 +65,16 @@ export default function TransactionConfirmation({ userAddress, btcAddress, activeWalletProvider, + refetchDepositHistory, + refetchAllDeposits, + setIsRefetching, }: TransactionConfirmationProps) { const { toast } = useToast(); const [btcTxStatus, setBtcTxStatus] = useState< "idle" | "pending" | "success" | "error" >("idle"); - const [btcTxId, setBtcTxId] = useState(""); - const [currentDepositId, setCurrentDepositId] = useState(null); + // const [btcTxId, setBtcTxId] = useState(""); + // const [currentDepositId, setCurrentDepositId] = useState(null); const { copiedText, copyToClipboard } = useClipboard(); // Get session state from Zustand store @@ -85,10 +91,67 @@ export default function TransactionConfirmation({ high: { rate: 5, fee: 0, time: "~10 min" }, }); + const [loadingFees, setLoadingFees] = useState(true); + const isP2SHAddress = (address: string): boolean => { return address.startsWith("3"); }; + const calculateFeeEstimate = (rate: number, txSize = 148): number => { + return Math.round(txSize * rate); + }; + + // Fetch fee rates as soon as the modal opens + useEffect(() => { + const fetchFeeEstimates = async () => { + if (open) { + setLoadingFees(true); + try { + // Get fee rate estimates from SDK or API + const feeRates = await styxSDK.getFeeEstimates(); + + // Ensure proper separation between tiers + const lowRate = feeRates.low || 1; + const mediumRate = Math.max(lowRate + 1, feeRates.medium || 2); + const highRate = Math.max(mediumRate + 1, feeRates.high || 5); + + // Calculate fees for a standard transaction (~148 vBytes) + const txSize = 148; + + setFeeEstimates({ + low: { + rate: lowRate, + fee: calculateFeeEstimate(lowRate, txSize), + time: "30 min", + }, + medium: { + rate: mediumRate, + fee: calculateFeeEstimate(mediumRate, txSize), + time: "~20 min", + }, + high: { + rate: highRate, + fee: calculateFeeEstimate(highRate, txSize), + time: "~10 min", + }, + }); + } catch (error) { + console.error("Error fetching fee estimates:", error); + // Fallback to default estimates with proper separation + setFeeEstimates({ + low: { rate: 1, fee: 148, time: "30 min" }, + medium: { rate: 2, fee: 296, time: "~20 min" }, + high: { rate: 5, fee: 740, time: "~10 min" }, + }); + } finally { + setLoadingFees(false); + } + } + }; + + fetchFeeEstimates(); + }, [open]); + const executeBitcoinTransaction = async (): Promise => { console.log( "Starting transaction with activeWalletProvider:", @@ -146,11 +209,12 @@ export default function TransactionConfirmation({ btcAmount: Number.parseFloat(confirmationData.depositAmount), stxReceiver: userAddress || "", btcSender: btcAddress || "", + isBlaze: confirmationData.isBlaze || false, }); console.log("Create deposit depositId:", depositId); // Store deposit ID for later use - setCurrentDepositId(depositId); + // setCurrentDepositId(depositId); try { if ( @@ -555,7 +619,7 @@ export default function TransactionConfirmation({ // Update state with success setBtcTxStatus("success"); - setBtcTxId(txid); + // setBtcTxId(txid); // Show success message toast({ @@ -568,6 +632,19 @@ export default function TransactionConfirmation({ // Close confirmation dialog onClose(); + + // Trigger data refetch with loading indicator + setIsRefetching(true); + Promise.all([refetchDepositHistory(), refetchAllDeposits()]).finally( + () => { + setIsRefetching(false); + // Optionally show a toast to confirm refresh + toast({ + title: "Data Refreshed", + description: "Your transaction history has been updated", + }); + } + ); } catch (err: any) { const error = err as Error; console.error("Error in Bitcoin transaction process:", error); @@ -726,7 +803,11 @@ export default function TransactionConfirmation({

Low

- {feeEstimates.low.fee} sats + {loadingFees ? ( + + ) : ( + `${feeEstimates.low.fee} sats` + )}

({feeEstimates.low.rate} sat/vB) @@ -747,7 +828,11 @@ export default function TransactionConfirmation({

Medium

- {feeEstimates.medium.fee} sats + {loadingFees ? ( + + ) : ( + `${feeEstimates.medium.fee} sats` + )}

({feeEstimates.medium.rate} sat/vB) @@ -766,7 +851,11 @@ export default function TransactionConfirmation({

High

- {feeEstimates.high.fee} sats + {loadingFees ? ( + + ) : ( + `${feeEstimates.high.fee} sats` + )}

({feeEstimates.high.rate} sat/vB) diff --git a/src/components/btc-deposit/all-deposits.tsx b/src/components/btc-deposit/all-deposits.tsx index fa21fcb3..a937468b 100644 --- a/src/components/btc-deposit/all-deposits.tsx +++ b/src/components/btc-deposit/all-deposits.tsx @@ -16,7 +16,16 @@ import { Badge } from "@/components/ui/badge"; import useResolveBnsOrAddress from "@/hooks/deposit/useResolveBnsOrAddress"; interface AllDepositsProps { - allDepositsHistory: Deposit[] | undefined; + allDepositsHistory: + | { + aggregateData: { + totalDeposits: number; + totalVolume: string; + uniqueUsers: number; + }; + recentDeposits: Deposit[]; + } + | undefined; isLoading: boolean; btcUsdPrice: number | null | undefined; isRefetching?: boolean; @@ -26,9 +35,10 @@ interface AllDepositsProps { const AddressCell = ({ address }: { address: string }) => { const { data } = useResolveBnsOrAddress(address); - const displayAddress = data?.resolvedValue - ? data.resolvedValue - : formatAddress(address); + const displayAddress = + data?.resolvedValue && !data.resolvedValue.startsWith("SP") + ? data.resolvedValue // Show BNS names + : formatAddress(address); const bgColor = getBackgroundColor(address); @@ -102,13 +112,13 @@ export default function AllDeposits({ ): "default" | "secondary" | "destructive" | "outline" => { switch (status) { case "broadcast": - return "secondary"; + return "secondary"; // yellow equivalent case "processing": - return "default"; + return "default"; // blue equivalent case "confirmed": - return "outline"; + return "outline"; // green equivalent default: - return "destructive"; + return "destructive"; // gray equivalent } }; @@ -138,37 +148,40 @@ export default function AllDeposits({ } }; - // Calculate stats - const totalDeposits = allDepositsHistory?.length || 0; - const totalVolume = - allDepositsHistory?.reduce((sum, deposit) => sum + deposit.btcAmount, 0) || - 0; - const uniqueUsers = allDepositsHistory - ? new Set(allDepositsHistory.map((deposit) => deposit.stxReceiver)).size - : 0; + // Check if we have data to display + const hasData = + allDepositsHistory && + allDepositsHistory.recentDeposits && + allDepositsHistory.recentDeposits.length > 0; return (

Recent Network Activity

{/* Stats summary box */} - {!isLoading && allDepositsHistory && allDepositsHistory.length > 0 && ( + {!isLoading && hasData && (

Total Deposits

-

{totalDeposits}

+

+ {allDepositsHistory.aggregateData.totalDeposits} +

Total Volume

- {formatBtcAmount(totalVolume)} + {Number.parseFloat( + allDepositsHistory.aggregateData.totalVolume + ).toFixed(8)}

BTC

Unique Users

-

{uniqueUsers}

+

+ {allDepositsHistory.aggregateData.uniqueUsers} +

@@ -181,14 +194,14 @@ export default function AllDeposits({ Loading network activity...

- ) : allDepositsHistory && allDepositsHistory.length > 0 ? ( + ) : hasData ? ( {/* Add refetching overlay */} {isRefetching && (
-

Updating data...

+

Updating history...

)} @@ -204,7 +217,7 @@ export default function AllDeposits({ - {allDepositsHistory.map((deposit: Deposit) => ( + {allDepositsHistory.recentDeposits.map((deposit: Deposit) => ( {formatTimeAgo(deposit.createdAt)} From 43ff9bb4ec74e37f9940525e4bb850017dedeedb Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Tue, 6 May 2025 12:24:28 +0545 Subject: [PATCH 18/29] fix: set wallet providers dynamically --- src/components/btc-deposit/DepositForm.tsx | 5 +- src/components/btc-deposit/index.tsx | 83 +++++++++++++++++++--- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index 359c689a..1beaff22 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -17,10 +17,7 @@ import { AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; -import { Badge } from "@/components/ui/badge"; import { useQuery } from "@tanstack/react-query"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; interface DepositFormProps { btcUsdPrice: number | null; @@ -43,7 +40,7 @@ export default function DepositForm({ setConfirmationData, setShowConfirmation, }: DepositFormProps) { - const [amount, setAmount] = useState("0.001"); + const [amount, setAmount] = useState("0.0001"); const [selectedPreset, setSelectedPreset] = useState(null); const { toast } = useToast(); const [useBlazeSubnet, setUseBlazeSubnet] = useState(false); diff --git a/src/components/btc-deposit/index.tsx b/src/components/btc-deposit/index.tsx index 2087b5f9..6ddf1e0f 100644 --- a/src/components/btc-deposit/index.tsx +++ b/src/components/btc-deposit/index.tsx @@ -1,11 +1,11 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { TransactionPriority } from "@faktoryfun/styx-sdk"; import { Card } from "@/components/ui/card"; import { Loader2 } from "lucide-react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import DepositForm, { type ConfirmationData } from "./DepositForm"; +import DepositForm from "./DepositForm"; import TransactionConfirmation from "./TransactionConfirmation"; import MyHistory from "./my-history"; import AllDeposits from "./all-deposits"; @@ -17,6 +17,15 @@ import useSdkPoolStatus from "@/hooks/deposit/useSdkPoolStatus"; import useSdkDepositHistory from "@/hooks/deposit/useSdkDepositHistory"; import useSdkAllDepositsHistory from "@/hooks/deposit/useSdkAllDepositsHistory"; +// Define the ConfirmationData type +export type ConfirmationData = { + depositAmount: string; + depositAddress: string; + stxAddress: string; + opReturnHex: string; + isBlaze?: boolean; +}; + export default function BitcoinDeposit() { // Get session state from Zustand store const { accessToken } = useSessionStore(); @@ -30,8 +39,48 @@ export default function BitcoinDeposit() { ); const [activeWalletProvider, setActiveWalletProvider] = useState< "leather" | "xverse" | null - >("xverse"); + >(null); const [activeTab, setActiveTab] = useState("deposit"); + const [isRefetching, setIsRefetching] = useState(false); + console.log(activeWalletProvider); + // Add this useEffect hook after the state declarations + useEffect(() => { + if (accessToken) { + // Detect wallet provider based on the structure of btcAddress + let detectedWalletProvider: "xverse" | "leather" | null = null; + + // Get user data from localStorage + const blockstackSession = JSON.parse( + localStorage.getItem("blockstack-session") || "{}" + ); + const userData = blockstackSession.userData; + + if (userData?.profile) { + // Check structure of btcAddress to determine wallet type + if (typeof userData.profile.btcAddress === "string") { + // Xverse stores btcAddress as a direct string + detectedWalletProvider = "xverse"; + } else if ( + userData.profile.btcAddress?.p2wpkh?.mainnet || + userData.profile.btcAddress?.p2tr?.mainnet + ) { + // Leather stores addresses in a structured object + detectedWalletProvider = "leather"; + } else { + // If no BTC address in profile, check localStorage + const storedBtcAddress = localStorage.getItem("btcAddress"); + if (storedBtcAddress) { + detectedWalletProvider = "leather"; // Assume Leather if using localStorage + } + } + + // Update the wallet provider if detected + if (detectedWalletProvider !== activeWalletProvider) { + setActiveWalletProvider(detectedWalletProvider); + } + } + } + }, [accessToken, activeWalletProvider]); // Get addresses directly const userAddress = accessToken ? getStacksAddress() : null; @@ -51,6 +100,7 @@ export default function BitcoinDeposit() { data: depositHistory, isLoading: isHistoryLoading, isRefetching: isHistoryRefetching, + refetch: refetchDepositHistory, } = useSdkDepositHistory(userAddress); // All network deposits - using the provided hook @@ -58,6 +108,7 @@ export default function BitcoinDeposit() { data: allDepositsHistory, isLoading: isAllDepositsLoading, isRefetching: isAllDepositsRefetching, + refetch: refetchAllDeposits, } = useSdkAllDepositsHistory(); // Determine if we're still loading critical data @@ -104,7 +155,7 @@ export default function BitcoinDeposit() {
- + Deposit My History All Deposits @@ -139,18 +190,29 @@ export default function BitcoinDeposit() { @@ -165,6 +227,9 @@ export default function BitcoinDeposit() { userAddress={userAddress || ""} btcAddress={btcAddress || ""} activeWalletProvider={activeWalletProvider} + refetchDepositHistory={refetchDepositHistory} + refetchAllDeposits={refetchAllDeposits} + setIsRefetching={setIsRefetching} /> )}
From ea845eb00b6a9e29549c3bc5a15d247425c2952f Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Tue, 6 May 2025 12:25:08 +0545 Subject: [PATCH 19/29] fix: update the address utils to get btc address from leather too --- src/lib/address.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib/address.ts b/src/lib/address.ts index 73d39424..70a7d1e2 100644 --- a/src/lib/address.ts +++ b/src/lib/address.ts @@ -13,15 +13,31 @@ export function getStacksAddress(): string | null { return address || null } -export function getBitcoinAddress(): string | null { +export function getBitcoinAddress(network: "mainnet" | "testnet" = "mainnet"): string | null { if (typeof window === "undefined") { return null } const blockstackSession = JSON.parse(localStorage.getItem("blockstack-session") || "{}") - - // Access the Bitcoin address from the profile const btcAddress = blockstackSession.userData?.profile?.btcAddress - return btcAddress || null -} \ No newline at end of file + if (!btcAddress) { + // Check if there's a stored address in localStorage as fallback + const storedBtcAddress = localStorage.getItem("btcAddress") + return storedBtcAddress || null + } + + // Handle Leather wallet's structured address format + if (typeof btcAddress === "object") { + // Leather wallet stores addresses in a structured object + if (network === "mainnet") { + // Try p2wpkh (segwit) first, then p2tr (taproot), then p2pkh (legacy) + return btcAddress.p2wpkh?.mainnet || btcAddress.p2tr?.mainnet || btcAddress.p2pkh?.mainnet || null + } else { + // For testnet, try the same address types but for testnet + return btcAddress.p2wpkh?.testnet || btcAddress.p2tr?.testnet || btcAddress.p2pkh?.testnet || null + } + } + + return typeof btcAddress === "string" ? btcAddress : null +} From 863ed3776024064b3a0e6da6bbbef0486df27fb0 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Tue, 6 May 2025 12:26:47 +0545 Subject: [PATCH 20/29] update: styx sdk --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 57a62dff..6579f29f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.0.14", + "@faktoryfun/styx-sdk": "^1.1.2", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", @@ -380,9 +380,9 @@ "license": "MIT" }, "node_modules/@faktoryfun/styx-sdk": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.0.14.tgz", - "integrity": "sha512-dVG304nHJglLYn1QeNKOmA14v5C00l+1y225DUE9hjzODnG3z1/1zdiLfZBzc1lGgrGALvDvEqI+HNCfpP5ELg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.1.2.tgz", + "integrity": "sha512-0YwnhaN8u7JatnzTyA7UHXtDyI7cheaHMa3miFYopkZgB9g7qTb6gzh5oDUhpgnsV8mCtzxrIqMhNciit3oJ3Q==", "license": "MIT" }, "node_modules/@fastify/busboy": { diff --git a/package.json b/package.json index 1503cb5d..2485cee3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.0.14", + "@faktoryfun/styx-sdk": "^1.1.2", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", From b5abbff4614b79cf21a45bc1c2f168fd4241df33 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 10:52:51 +0545 Subject: [PATCH 21/29] update(dep): styx-sdk --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6579f29f..dcd53f5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.1.2", + "@faktoryfun/styx-sdk": "^1.1.4", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", @@ -380,9 +380,9 @@ "license": "MIT" }, "node_modules/@faktoryfun/styx-sdk": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.1.2.tgz", - "integrity": "sha512-0YwnhaN8u7JatnzTyA7UHXtDyI7cheaHMa3miFYopkZgB9g7qTb6gzh5oDUhpgnsV8mCtzxrIqMhNciit3oJ3Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@faktoryfun/styx-sdk/-/styx-sdk-1.1.4.tgz", + "integrity": "sha512-OKU54tsJxPY3593BrnZ0g8nCmx0XTJ7bd9mbygETBQzoOKAwNFt7aPVzcQ+KvhPgGDKvrLo0daF/qGf4FiPMeg==", "license": "MIT" }, "node_modules/@fastify/busboy": { diff --git a/package.json b/package.json index 2485cee3..0cca9521 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faktoryfun/core-sdk": "^0.2.18", - "@faktoryfun/styx-sdk": "^1.1.2", + "@faktoryfun/styx-sdk": "^1.1.4", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@radix-ui/react-accordion": "^1.2.2", From c26b95e66e38c81d668007d1ea0017f880c256a7 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 11:30:48 +0545 Subject: [PATCH 22/29] fix: any types --- .../btc-deposit/TransactionConfirmation.tsx | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index 45bb9662..f19aa511 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -9,6 +9,10 @@ import { useToast } from "@/hooks/use-toast"; import { ArrowLeft, Copy, Check, AlertTriangle, Loader2 } from "lucide-react"; import { useSessionStore } from "@/store/session"; import { useClipboard } from "@/helpers/clipboard-utils"; +import type { + QueryObserverResult, + RefetchOptions, +} from "@tanstack/react-query"; import { Dialog, @@ -22,6 +26,13 @@ import { Card, CardContent } from "@/components/ui/card"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { cn } from "@/lib/utils"; import type { ConfirmationData } from "./DepositForm"; +import type { + TransactionPrepareParams, + PreparedTransactionData, + DepositStatus, + DepositHistoryResponse, + Deposit, +} from "@faktoryfun/styx-sdk"; // Add this to fix the window.LeatherProvider type error declare global { @@ -39,8 +50,12 @@ interface TransactionConfirmationProps { userAddress: string; btcAddress: string; activeWalletProvider: "leather" | "xverse" | null; - refetchDepositHistory: () => Promise; - refetchAllDeposits: () => Promise; + refetchDepositHistory: ( + options?: RefetchOptions + ) => Promise>; + refetchAllDeposits: ( + options?: RefetchOptions + ) => Promise>; setIsRefetching: (isRefetching: boolean) => void; } @@ -85,7 +100,11 @@ export default function TransactionConfirmation({ initialize(); }, [initialize]); - const [feeEstimates, setFeeEstimates] = useState({ + const [feeEstimates, setFeeEstimates] = useState<{ + low: { rate: number; fee: number; time: string }; + medium: { rate: number; fee: number; time: string }; + high: { rate: number; fee: number; time: string }; + }>({ low: { rate: 1, fee: 0, time: "30 min" }, medium: { rate: 3, fee: 0, time: "~20 min" }, high: { rate: 5, fee: 0, time: "~10 min" }, @@ -254,7 +273,7 @@ export default function TransactionConfirmation({ btcAddress, feePriority, walletProvider: activeWalletProvider, - }); + } as TransactionPrepareParams); // Here, update fee estimates from the prepared transaction setFeeEstimates({ @@ -290,7 +309,7 @@ export default function TransactionConfirmation({ inputCount: preparedTransaction.inputCount, outputCount: preparedTransaction.outputCount, inscriptionCount: preparedTransaction.inscriptionCount, - }, + } as PreparedTransactionData, walletProvider: activeWalletProvider, btcAddress: senderBtcAddress, }); @@ -599,7 +618,7 @@ export default function TransactionConfirmation({ id: depositId, data: { btcTxId: txid, - status: "broadcast", + status: "broadcast" as DepositStatus, }, }); @@ -654,7 +673,7 @@ export default function TransactionConfirmation({ await styxSDK.updateDepositStatus({ id: depositId, data: { - status: "canceled", + status: "canceled" as DepositStatus, }, }); From 497b2e660d4bbdb7fb4bbf36c907b4a7f800a7f7 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 11:35:26 +0545 Subject: [PATCH 23/29] fix: udpate types --- src/components/btc-deposit/DepositForm.tsx | 29 ++++++++++++++++------ src/components/btc-deposit/index.tsx | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index 1beaff22..624959ec 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -3,6 +3,13 @@ import { useState, type ChangeEvent, useEffect } from "react"; import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; import { styxSDK } from "@faktoryfun/styx-sdk"; +import { + FeeEstimates, + PoolStatus, + TransactionPrepareParams, + TransactionPriority, + UTXO, +} from "@faktoryfun/styx-sdk"; import { MIN_DEPOSIT_SATS, MAX_DEPOSIT_SATS } from "@faktoryfun/styx-sdk"; import { useToast } from "@/hooks/use-toast"; import { Bitcoin, Loader2, Zap } from "lucide-react"; @@ -21,7 +28,7 @@ import { useQuery } from "@tanstack/react-query"; interface DepositFormProps { btcUsdPrice: number | null; - poolStatus: any; + poolStatus: PoolStatus | null; setConfirmationData: (data: ConfirmationData) => void; setShowConfirmation: (show: boolean) => void; } @@ -44,7 +51,11 @@ export default function DepositForm({ const [selectedPreset, setSelectedPreset] = useState(null); const { toast } = useToast(); const [useBlazeSubnet, setUseBlazeSubnet] = useState(false); - const [feeEstimates, setFeeEstimates] = useState({ + const [feeEstimates, setFeeEstimates] = useState<{ + low: { rate: number; fee: number; time: string }; + medium: { rate: number; fee: number; time: string }; + high: { rate: number; fee: number; time: string }; + }>({ low: { rate: 1, fee: 0, time: "30 min" }, medium: { rate: 3, fee: 0, time: "~20 min" }, high: { rate: 5, fee: 0, time: "~10 min" }, @@ -83,7 +94,7 @@ export default function DepositForm({ const utxos = await response.json(); const totalSats = utxos.reduce( - (sum: number, utxo: any) => sum + utxo.value, + (sum: number, utxo: UTXO) => sum + utxo.value, 0 ); return totalSats / 100000000; // Convert satoshis to BTC @@ -95,7 +106,11 @@ export default function DepositForm({ }); // Fetch fee estimates from mempool.space - const fetchMempoolFeeEstimates = async () => { + const fetchMempoolFeeEstimates = async (): Promise<{ + low: { rate: number; fee: number; time: string }; + medium: { rate: number; fee: number; time: string }; + high: { rate: number; fee: number; time: string }; + }> => { try { console.log("Fetching fee estimates directly from mempool.space"); const response = await fetch( @@ -257,7 +272,7 @@ export default function DepositForm({ }); // Always fetch fresh fee estimates before transaction - let currentFeeRates; + let currentFeeRates: FeeEstimates; try { console.log( "Fetching fresh fee estimates before transaction preparation" @@ -335,10 +350,10 @@ export default function DepositForm({ amount: totalAmount, // Now includes service fee userAddress, btcAddress, - feePriority: "medium", + feePriority: "medium" as TransactionPriority, walletProvider: activeWalletProvider, feeRates: currentFeeRates, - }); + } as TransactionPrepareParams); console.log("Transaction prepared:", transactionData); diff --git a/src/components/btc-deposit/index.tsx b/src/components/btc-deposit/index.tsx index 6ddf1e0f..6baa7593 100644 --- a/src/components/btc-deposit/index.tsx +++ b/src/components/btc-deposit/index.tsx @@ -179,7 +179,7 @@ export default function BitcoinDeposit() { ) : ( From a4a54989c999bab51a17cdc31fe4084226db157e Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 11:57:58 +0545 Subject: [PATCH 24/29] fix: make eslint happy --- src/components/btc-deposit/DepositForm.tsx | 56 ++++++++++++------- .../btc-deposit/TransactionConfirmation.tsx | 6 +- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index 624959ec..f0bcf0f4 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -3,7 +3,7 @@ import { useState, type ChangeEvent, useEffect } from "react"; import { getStacksAddress, getBitcoinAddress } from "@/lib/address"; import { styxSDK } from "@faktoryfun/styx-sdk"; -import { +import type { FeeEstimates, PoolStatus, TransactionPrepareParams, @@ -12,7 +12,7 @@ import { } from "@faktoryfun/styx-sdk"; import { MIN_DEPOSIT_SATS, MAX_DEPOSIT_SATS } from "@faktoryfun/styx-sdk"; import { useToast } from "@/hooks/use-toast"; -import { Bitcoin, Loader2, Zap } from "lucide-react"; +import { Bitcoin, Loader2 } from "lucide-react"; import AuthButton from "@/components/home/auth-button"; import { useSessionStore } from "@/store/session"; import { Button } from "@/components/ui/button"; @@ -366,34 +366,52 @@ export default function DepositForm({ }); setShowConfirmation(true); - } catch (err: any) { + } catch (err) { console.error("Error preparing transaction:", err); - if (isInscriptionError(err)) { - handleInscriptionError(err); - } else if (isUtxoCountError(err)) { - handleUtxoCountError(err); - } else if (isAddressTypeError(err)) { - handleAddressTypeError(err, activeWalletProvider); + if (err instanceof Error) { + if (isInscriptionError(err)) { + handleInscriptionError(err); + } else if (isUtxoCountError(err)) { + handleUtxoCountError(err); + } else if (isAddressTypeError(err)) { + handleAddressTypeError(err, activeWalletProvider); + } else { + toast({ + title: "Error", + description: + err.message || + "Failed to prepare transaction. Please try again.", + variant: "destructive", + }); + } } else { toast({ title: "Error", - description: - err.message || "Failed to prepare transaction. Please try again.", + description: "Failed to prepare transaction. Please try again.", variant: "destructive", }); } } - } catch (err: any) { + } catch (err) { console.error("Error preparing Bitcoin transaction:", err); - toast({ - title: "Error", - description: - err.message || - "Failed to prepare Bitcoin transaction. Please try again.", - variant: "destructive", - }); + if (err instanceof Error) { + toast({ + title: "Error", + description: + err.message || + "Failed to prepare Bitcoin transaction. Please try again.", + variant: "destructive", + }); + } else { + toast({ + title: "Error", + description: + "Failed to prepare Bitcoin transaction. Please try again.", + variant: "destructive", + }); + } } }; diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index f19aa511..b44d4ae1 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -340,7 +340,7 @@ export default function TransactionConfirmation({ try { // First, try to get the account (which might fail if we don't have permission) console.log("Trying to get wallet account..."); - let walletAccount = await (xverseRequest as any)( + let walletAccount = await xverseRequest( "wallet_getAccount", null ); @@ -353,7 +353,7 @@ export default function TransactionConfirmation({ console.log("Access denied. Requesting permissions..."); // Request permissions using wallet_requestPermissions as shown in the docs - const permissionResponse = await (xverseRequest as any)( + const permissionResponse = await xverseRequest( "wallet_requestPermissions", null ); @@ -364,7 +364,7 @@ export default function TransactionConfirmation({ console.log( "Permission granted. Trying to get wallet account again..." ); - walletAccount = await (xverseRequest as any)( + walletAccount = await xverseRequest( "wallet_getAccount", null ); From 98794c9aa6ff6ad169d9441c0428fa0ab60b91a6 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 13:00:23 +0545 Subject: [PATCH 25/29] fix: cleanup unused vars --- src/components/btc-deposit/DepositForm.tsx | 22 +++++++++++++++---- .../btc-deposit/TransactionConfirmation.tsx | 16 +++++--------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index f0bcf0f4..d45f179a 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -50,7 +50,7 @@ export default function DepositForm({ const [amount, setAmount] = useState("0.0001"); const [selectedPreset, setSelectedPreset] = useState(null); const { toast } = useToast(); - const [useBlazeSubnet, setUseBlazeSubnet] = useState(false); + // const [useBlazeSubnet, setUseBlazeSubnet] = useState(false); const [feeEstimates, setFeeEstimates] = useState<{ low: { rate: number; fee: number; time: string }; medium: { rate: number; fee: number; time: string }; @@ -69,10 +69,24 @@ export default function DepositForm({ initialize(); }, [initialize]); + // Use the activeWalletProvider state with a default value const [activeWalletProvider, setActiveWalletProvider] = useState< "leather" | "xverse" | null >(null); + // Set the wallet provider based on the session when initialized + useEffect(() => { + if (accessToken) { + // Determine which wallet is being used based on available information + // This is a placeholder - implement your actual wallet detection logic here + const detectedProvider = localStorage.getItem("walletProvider") as + | "leather" + | "xverse" + | null; + setActiveWalletProvider(detectedProvider); + } + }, [accessToken]); + // Get addresses from the lib - only if we have a session const userAddress = accessToken ? getStacksAddress() : null; const btcAddress = accessToken ? getBitcoinAddress() : null; @@ -362,7 +376,7 @@ export default function DepositForm({ depositAddress: transactionData.depositAddress, stxAddress: userAddress, opReturnHex: transactionData.opReturnData, - isBlaze: useBlazeSubnet, + // isBlaze: useBlazeSubnet, }); setShowConfirmation(true); @@ -663,8 +677,8 @@ export default function DepositForm({

- Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin state - reading. No intermediaries or multi-signature scheme needed. + Your BTC deposit unlocks sBTC via Clarity's direct Bitcoin + state reading. No intermediaries or multi-signature scheme needed. Trustless. Fast. Secure.

diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index b44d4ae1..e38c1bd1 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -93,7 +93,7 @@ export default function TransactionConfirmation({ const { copiedText, copyToClipboard } = useClipboard(); // Get session state from Zustand store - const { accessToken, userId, isLoading, initialize } = useSessionStore(); + const { accessToken, isLoading, initialize } = useSessionStore(); // Initialize session on component mount useEffect(() => { @@ -380,9 +380,7 @@ export default function TransactionConfirmation({ walletAccount.result.addresses ) { // Find the payment address that matches our sender address - const paymentAddress = ( - walletAccount.result as any - ).addresses.find( + const paymentAddress = walletAccount.result.addresses.find( (addr: any) => addr.address === senderBtcAddress && addr.purpose === "payment" @@ -685,7 +683,7 @@ export default function TransactionConfirmation({ variant: "destructive", }); } - } catch (err: any) { + } catch (err) { const error = err as Error; console.error("Error creating deposit record:", error); setBtcTxStatus("error"); @@ -817,7 +815,7 @@ export default function TransactionConfirmation({ "rounded-lg overflow-hidden border border-zinc-700 hover:border-primary cursor-pointer", feePriority === "low" ? "bg-primary/20" : "bg-zinc-900" )} - onClick={() => setFeePriority(TransactionPriority.Low as any)} + onClick={() => setFeePriority(TransactionPriority.Low)} >

Low

@@ -840,9 +838,7 @@ export default function TransactionConfirmation({ "rounded-lg overflow-hidden border border-zinc-700 hover:border-primary cursor-pointer", feePriority === "medium" ? "bg-primary/20" : "bg-zinc-900" )} - onClick={() => - setFeePriority(TransactionPriority.Medium as any) - } + onClick={() => setFeePriority(TransactionPriority.Medium)} >

Medium

@@ -865,7 +861,7 @@ export default function TransactionConfirmation({ "rounded-lg overflow-hidden border border-zinc-700 hover:border-primary cursor-pointer", feePriority === "high" ? "bg-primary/20" : "bg-zinc-900" )} - onClick={() => setFeePriority(TransactionPriority.High as any)} + onClick={() => setFeePriority(TransactionPriority.High)} >

High

From 90b876212eab98351cd489e55ee8c0251c5c4c05 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 13:02:05 +0545 Subject: [PATCH 26/29] fix: any types --- .../btc-deposit/TransactionConfirmation.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index e38c1bd1..c62f0d03 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -4,7 +4,11 @@ import { useState, useEffect } from "react"; import { hex } from "@scure/base"; import * as btc from "@scure/btc-signer"; import { styxSDK, TransactionPriority } from "@faktoryfun/styx-sdk"; -import { request as xverseRequest } from "sats-connect"; +import { + AddressPurpose, + AddressType, + request as xverseRequest, +} from "sats-connect"; import { useToast } from "@/hooks/use-toast"; import { ArrowLeft, Copy, Check, AlertTriangle, Loader2 } from "lucide-react"; import { useSessionStore } from "@/store/session"; @@ -381,7 +385,13 @@ export default function TransactionConfirmation({ ) { // Find the payment address that matches our sender address const paymentAddress = walletAccount.result.addresses.find( - (addr: any) => + (addr: { + address: string; + walletType: "software" | "ledger" | "keystone"; + publicKey: string; + purpose: AddressPurpose; + addressType: AddressType; + }) => addr.address === senderBtcAddress && addr.purpose === "payment" ); From 36b249647979f11ab2b8c8cef42dc6fbc555eb72 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 13:41:09 +0545 Subject: [PATCH 27/29] fix: create custom types for Leatherprovider to avoid any --- .../btc-deposit/TransactionConfirmation.tsx | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/components/btc-deposit/TransactionConfirmation.tsx b/src/components/btc-deposit/TransactionConfirmation.tsx index c62f0d03..341aa7d3 100644 --- a/src/components/btc-deposit/TransactionConfirmation.tsx +++ b/src/components/btc-deposit/TransactionConfirmation.tsx @@ -5,8 +5,8 @@ import { hex } from "@scure/base"; import * as btc from "@scure/btc-signer"; import { styxSDK, TransactionPriority } from "@faktoryfun/styx-sdk"; import { - AddressPurpose, - AddressType, + type AddressPurpose, + type AddressType, request as xverseRequest, } from "sats-connect"; import { useToast } from "@/hooks/use-toast"; @@ -38,10 +38,35 @@ import type { Deposit, } from "@faktoryfun/styx-sdk"; +export interface LeatherSignPsbtRequestParams { + hex: string; + network: string; + broadcast: boolean; + allowedSighash?: number[]; + allowUnknownOutputs?: boolean; +} + +export interface LeatherSignPsbtResponse { + result?: { + hex: string; + }; + error?: { + code?: string; + message?: string; + }; +} + +export interface LeatherProvider { + request( + method: "signPsbt", + params: LeatherSignPsbtRequestParams + ): Promise; +} + // Add this to fix the window.LeatherProvider type error declare global { interface Window { - LeatherProvider: any; + LeatherProvider?: LeatherProvider; } } @@ -672,9 +697,8 @@ export default function TransactionConfirmation({ }); } ); - } catch (err: any) { - const error = err as Error; - console.error("Error in Bitcoin transaction process:", error); + } catch (err: unknown) { + console.error("Error in Bitcoin transaction process:", err); setBtcTxStatus("error"); // Update deposit as canceled if wallet interaction failed @@ -685,17 +709,19 @@ export default function TransactionConfirmation({ }, }); + const errorMessage = + err instanceof Error + ? err.message + : "Failed to process Bitcoin transaction. Please try again."; + toast({ title: "Error", - description: - error.message || - "Failed to process Bitcoin transaction. Please try again.", + description: errorMessage, variant: "destructive", }); } - } catch (err) { - const error = err as Error; - console.error("Error creating deposit record:", error); + } catch (err: unknown) { + console.error("Error creating deposit record:", err); setBtcTxStatus("error"); toast({ From cad59cfa33bfe48f79ecddcdac6e3517e7b8cbe1 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Wed, 7 May 2025 21:29:19 +0545 Subject: [PATCH 28/29] fix: remove duplicate address detection --- src/components/btc-deposit/DepositForm.tsx | 30 ++++++++++++---------- src/components/btc-deposit/index.tsx | 1 + 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/btc-deposit/DepositForm.tsx b/src/components/btc-deposit/DepositForm.tsx index d45f179a..84c419b6 100644 --- a/src/components/btc-deposit/DepositForm.tsx +++ b/src/components/btc-deposit/DepositForm.tsx @@ -31,6 +31,7 @@ interface DepositFormProps { poolStatus: PoolStatus | null; setConfirmationData: (data: ConfirmationData) => void; setShowConfirmation: (show: boolean) => void; + activeWalletProvider: "leather" | "xverse" | null; } export interface ConfirmationData { @@ -46,6 +47,7 @@ export default function DepositForm({ poolStatus, setConfirmationData, setShowConfirmation, + activeWalletProvider, }: DepositFormProps) { const [amount, setAmount] = useState("0.0001"); const [selectedPreset, setSelectedPreset] = useState(null); @@ -70,22 +72,22 @@ export default function DepositForm({ }, [initialize]); // Use the activeWalletProvider state with a default value - const [activeWalletProvider, setActiveWalletProvider] = useState< - "leather" | "xverse" | null - >(null); + // const [activeWalletProvider, setActiveWalletProvider] = useState< + // "leather" | "xverse" | null + // >(null); // Set the wallet provider based on the session when initialized - useEffect(() => { - if (accessToken) { - // Determine which wallet is being used based on available information - // This is a placeholder - implement your actual wallet detection logic here - const detectedProvider = localStorage.getItem("walletProvider") as - | "leather" - | "xverse" - | null; - setActiveWalletProvider(detectedProvider); - } - }, [accessToken]); + // useEffect(() => { + // if (accessToken) { + // // Determine which wallet is being used based on available information + // // This is a placeholder - implement your actual wallet detection logic here + // const detectedProvider = localStorage.getItem("walletProvider") as + // | "leather" + // | "xverse" + // | null; + // setActiveWalletProvider(detectedProvider); + // } + // }, [accessToken]); // Get addresses from the lib - only if we have a session const userAddress = accessToken ? getStacksAddress() : null; diff --git a/src/components/btc-deposit/index.tsx b/src/components/btc-deposit/index.tsx index 6baa7593..e215794c 100644 --- a/src/components/btc-deposit/index.tsx +++ b/src/components/btc-deposit/index.tsx @@ -182,6 +182,7 @@ export default function BitcoinDeposit() { poolStatus={poolStatus ?? null} setConfirmationData={setConfirmationData} setShowConfirmation={setShowConfirmation} + activeWalletProvider={activeWalletProvider} /> )} From 77ead73081268f258c5da992ea795b779c8d1351 Mon Sep 17 00:00:00 2001 From: biwasbhandari Date: Thu, 8 May 2025 21:31:59 +0545 Subject: [PATCH 29/29] fix: conflicts --- package-lock.json | 128 +++++++++++++++++++++++++++++++++++++++------- package.json | 2 + 2 files changed, 111 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 491174ff..3ecd65f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@radix-ui/react-tabs": "^1.1.11", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-tooltip": "^1.2.6", + "@scure/btc-signer": "^1.8.0", "@stacks/connect": "^7.10.2", "@stacks/connect-react": "^22.6.2", "@stacks/connect-ui": "^6.6.0", @@ -59,6 +60,7 @@ "react-markdown": "^9.1.0", "recharts": "^2.15.3", "remark-gfm": "^4.0.1", + "sats-connect": "^3.5.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "zustand": "^5.0.4" @@ -2181,9 +2183,9 @@ "license": "MIT" }, "node_modules/@sats-connect/core": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.6.2.tgz", - "integrity": "sha512-/vQsg6OnYA2+BN4VHi3xHPJYYz4K3ENjVfCjgK4RqAaMu14gZSaZEtcDQgyfuEmI4Hi3vI/yNqbJ3R8yC4anlw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.6.5.tgz", + "integrity": "sha512-jltPdG3RzY6NPSp/ldoVTu7hOmMiuXr90osTHFPoQzprxcOs/1U4sZt/qAyGhfOM6B+ZXb7DF686BmFkUpPCJA==", "license": "ISC", "dependencies": { "axios": "1.8.4", @@ -4620,6 +4622,12 @@ "license": "MIT", "peer": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "dev": true, @@ -4642,6 +4650,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "dev": true, @@ -4863,7 +4882,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5095,6 +5113,18 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "license": "MIT", @@ -5441,6 +5471,15 @@ "license": "MIT", "peer": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "dev": true, @@ -5527,7 +5566,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5686,7 +5724,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5694,7 +5731,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5734,7 +5770,6 @@ }, "node_modules/es-object-atoms": { "version": "1.0.0", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5747,7 +5782,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6496,6 +6530,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "dev": true, @@ -6518,6 +6572,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "dev": true, @@ -6707,7 +6776,6 @@ }, "node_modules/get-intrinsic": { "version": "1.2.6", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -6874,7 +6942,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6952,7 +7019,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6963,7 +7029,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -7899,7 +7964,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8742,6 +8806,27 @@ "node": ">=10.0.0" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "dev": true, @@ -9730,6 +9815,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.2", "dev": true, @@ -10372,12 +10463,12 @@ "peer": true }, "node_modules/sats-connect": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-3.4.0.tgz", - "integrity": "sha512-nm7uLwY7CEr8EWChXyNwibakI5ebCo8fJmcqTEOrQMSjJNUk/tTuyP7HHVV4prlE1g666e1I3wbB+J7dUJjS5A==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-3.5.0.tgz", + "integrity": "sha512-/Czx9XcBV57ubAMrII3WaQG4kq7imdgGUOU9IRg0/8AWdjFczOq9wu1i7vCnxENm8MoRLuCfhMIHbekxUn23HA==", "license": "MIT", "dependencies": { - "@sats-connect/core": "0.6.2", + "@sats-connect/core": "0.6.5", "@sats-connect/make-default-provider-config": "0.0.10", "@sats-connect/ui": "0.0.7" } @@ -11418,7 +11509,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 1325c88d..99c0397b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-tabs": "^1.1.11", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-tooltip": "^1.2.6", + "@scure/btc-signer": "^1.8.0", "@stacks/connect": "^7.10.2", "@stacks/connect-react": "^22.6.2", "@stacks/connect-ui": "^6.6.0", @@ -63,6 +64,7 @@ "react-markdown": "^9.1.0", "recharts": "^2.15.3", "remark-gfm": "^4.0.1", + "sats-connect": "^3.5.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "zustand": "^5.0.4"