diff --git a/src/components/common/card-content.tsx b/src/components/common/card-content.tsx index f2070be..574d0ae 100644 --- a/src/components/common/card-content.tsx +++ b/src/components/common/card-content.tsx @@ -16,10 +16,13 @@ export default function CardUI({ cardClassName?: string; headerDom?: ReactNode; }) { + // Make title larger for wallet info card (col-span-2) + const isLargeTitle = cardClassName?.includes('col-span-2'); + return ( - {title} + {title} {headerDom && headerDom} {icon && ( <> @@ -33,7 +36,7 @@ export default function CardUI({ )} - +
{description && (

{description}

diff --git a/src/components/common/discordIcon.tsx b/src/components/common/discordIcon.tsx index ec9e657..e8743f8 100644 --- a/src/components/common/discordIcon.tsx +++ b/src/components/common/discordIcon.tsx @@ -1,4 +1,8 @@ -export default function DiscordIcon() { +interface DiscordIconProps { + className?: string; +} + +export default function DiscordIcon({ className }: DiscordIconProps) { return ( diff --git a/src/components/common/overall-layout/layout.tsx b/src/components/common/overall-layout/layout.tsx index 34ba872..dbbeda9 100644 --- a/src/components/common/overall-layout/layout.tsx +++ b/src/components/common/overall-layout/layout.tsx @@ -371,19 +371,35 @@ export default function RootLayout({ // Don't refetch here - let the natural query refetch handle it if needed }, []); - const handleAuthModalAuthorized = useCallback(() => { + const handleAuthModalAuthorized = useCallback(async () => { setShowAuthModal(false); setCheckingSession(false); setHasCheckedSession(true); // Mark as checked so we don't check again // Show loading skeleton for smooth transition setShowPostAuthLoading(true); - // Refetch session after authorization to update state (but don't show modal again) - void refetchWalletSession(); + + // Wait a moment for the cookie to be set by the browser, then refetch session + await new Promise(resolve => setTimeout(resolve, 200)); + + // Refetch session to update state + await refetchWalletSession(); + + // Invalidate wallet queries so they refetch with the new session + // Use a small delay to ensure cookie is available on subsequent requests + setTimeout(() => { + const userAddressForInvalidation = userAddress || address; + if (userAddressForInvalidation) { + void ctx.wallet.getUserWallets.invalidate({ address: userAddressForInvalidation }); + void ctx.wallet.getUserNewWallets.invalidate({ address: userAddressForInvalidation }); + void ctx.wallet.getUserNewWalletsNotOwner.invalidate({ address: userAddressForInvalidation }); + } + }, 300); + // Hide loading after a brief delay to allow data to load setTimeout(() => { setShowPostAuthLoading(false); - }, 1000); - }, [refetchWalletSession]); + }, 1500); + }, [refetchWalletSession, ctx.wallet, userAddress, address]); // Memoize computed route values const isWalletPath = useMemo(() => router.pathname.includes("/wallets/[wallet]"), [router.pathname]); diff --git a/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx b/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx index 18d7e2e..4ae3e8d 100644 --- a/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx +++ b/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx @@ -160,8 +160,9 @@ export default function WalletDataLoaderWrapper({ } function dRepIds() { - // Use multisig wallet DRep ID if available, otherwise fallback to appWallet - const dRepId = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getDRepId() : appWallet?.dRepId; + // Use multisig wallet DRep ID if available (it handles no DRep keys by using payment script), + // otherwise fallback to appWallet (for legacy wallets without multisigWallet) + const dRepId = multisigWallet ? multisigWallet.getDRepId() : appWallet?.dRepId; if (!dRepId) return null; return getDRepIds(dRepId); } diff --git a/src/components/multisig/proxy/ProxyOverview.tsx b/src/components/multisig/proxy/ProxyOverview.tsx index 5041211..28e286d 100644 --- a/src/components/multisig/proxy/ProxyOverview.tsx +++ b/src/components/multisig/proxy/ProxyOverview.tsx @@ -1,4 +1,5 @@ import React, { memo, useState, useEffect } from "react"; +import { truncateTokenSymbol } from "@/utils/strings"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -270,7 +271,7 @@ const ProxyCard = memo(function ProxyCard({
{displayBalance.map((asset: any, index: number) => (
- {asset.unit === "lovelace" ? "ADA" : asset.unit}: + {asset.unit === "lovelace" ? "ADA" : truncateTokenSymbol(asset.unit)}: {asset.unit === "lovelace" ? `${(parseFloat(asset.quantity) / 1000000).toFixed(6)} ADA` diff --git a/src/components/multisig/proxy/ProxySpend.tsx b/src/components/multisig/proxy/ProxySpend.tsx index 4ecd8af..93340f3 100644 --- a/src/components/multisig/proxy/ProxySpend.tsx +++ b/src/components/multisig/proxy/ProxySpend.tsx @@ -1,4 +1,5 @@ import React, { memo } from "react"; +import { truncateTokenSymbol } from "@/utils/strings"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -139,7 +140,7 @@ const ProxySpend = memo(function ProxySpend({
- {asset.unit === "lovelace" ? "ADA" : asset.unit} + {asset.unit === "lovelace" ? "ADA" : truncateTokenSymbol(asset.unit)} {asset.unit === "lovelace" diff --git a/src/components/pages/homepage/wallets/index.tsx b/src/components/pages/homepage/wallets/index.tsx index 645af3f..523c10b 100644 --- a/src/components/pages/homepage/wallets/index.tsx +++ b/src/components/pages/homepage/wallets/index.tsx @@ -9,10 +9,12 @@ import { getFirstAndLast } from "@/utils/strings"; import { api } from "@/utils/api"; import { useUserStore } from "@/lib/zustand/user"; import { useSiteStore } from "@/lib/zustand/site"; -import { buildMultisigWallet } from "@/utils/common"; +import { buildMultisigWallet, getWalletType } from "@/utils/common"; import { addressToNetwork } from "@/utils/multisigSDK"; import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Archive } from "lucide-react"; import PageHeader from "@/components/common/page-header"; import CardUI from "@/components/common/card-content"; import RowLabelInfo from "@/components/common/row-label-info"; @@ -29,10 +31,21 @@ export default function PageWallets() { const [showArchived, setShowArchived] = useState(false); const userAddress = useUserStore((state) => state.userAddress); + // Check wallet session authorization before enabling queries + const { data: walletSession } = api.auth.getWalletSession.useQuery( + { address: userAddress ?? "" }, + { + enabled: !!userAddress && userAddress.length > 0, + refetchOnWindowFocus: false, + }, + ); + const isAuthorized = walletSession?.authorized ?? false; + const { data: newPendingWallets, isLoading: isLoadingNewWallets } = api.wallet.getUserNewWallets.useQuery( { address: userAddress! }, { - enabled: userAddress !== undefined, + // Only enable query when user is authorized (prevents 403 errors) + enabled: userAddress !== undefined && isAuthorized, retry: (failureCount, error) => { // Don't retry on authorization errors (403) if (error && typeof error === "object") { @@ -60,15 +73,24 @@ export default function PageWallets() { api.wallet.getUserNewWalletsNotOwner.useQuery( { address: userAddress! }, { - enabled: userAddress !== undefined, + // Only enable query when user is authorized (prevents 403 errors) + enabled: userAddress !== undefined && isAuthorized, retry: (failureCount, error) => { // Don't retry on authorization errors (403) if (error && typeof error === "object") { - const err = error as { code?: string; message?: string; data?: { code?: string } }; + const err = error as { + code?: string; + message?: string; + data?: { code?: string; httpStatus?: number }; + shape?: { code?: string; message?: string }; + }; + const errorMessage = err.message || err.shape?.message || ""; const isAuthError = err.code === "FORBIDDEN" || err.data?.code === "FORBIDDEN" || - err.message?.includes("Address mismatch"); + err.data?.httpStatus === 403 || + err.shape?.code === "FORBIDDEN" || + errorMessage.includes("Address mismatch"); if (isAuthError) return false; } return failureCount < 1; @@ -216,6 +238,11 @@ function CardWallet({ walletId: wallet.id, }); + // Check wallet type for badge display using centralized detection + const walletType = getWalletType(wallet); + const isSummonWallet = walletType === 'summon'; + const isLegacyWallet = walletType === 'legacy'; + // Rebuild the multisig wallet to get the correct canonical address for display // This ensures we show the correct address even if wallet.address was built incorrectly const displayAddress = useMemo(() => { @@ -240,6 +267,17 @@ function CardWallet({ title={`${wallet.name}${wallet.isArchived ? " (Archived)" : ""}`} description={wallet.description} cardClassName="" + headerDom={ + isSummonWallet ? ( + + + Summon + + ) : undefined + } > p.id === selectedProxyId); if (!proxy) throw new Error("Proxy not found"); - if (!multisigWallet) throw new Error("Multisig Wallet could not be built."); const meshTxBuilder = getTxBuilder(network); const proxyContract = new MeshProxyContract( { @@ -314,7 +313,12 @@ export default function BallotCard({ }); // Vote using proxy - const txBuilder = await proxyContract.voteProxyDrep(votes, utxos, multisigWallet?.getScript().address); + // Use multisig wallet address if available, otherwise fallback to appWallet (for legacy wallets) + const proxyAddress = multisigWallet?.getScript().address || appWallet?.address; + if (!proxyAddress) { + throw new Error("Wallet address not found"); + } + const txBuilder = await proxyContract.voteProxyDrep(votes, utxos, proxyAddress); await newTransaction({ txBuilder: txBuilder, @@ -401,8 +405,9 @@ export default function BallotCard({ setLoading(true); try { - if (!multisigWallet) throw new Error("Multisig Wallet could not be built."); - const dRepId = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getDRepId() : appWallet?.dRepId; + // Use multisig wallet DRep ID if available (it handles no DRep keys by using payment script), + // otherwise fallback to appWallet (for legacy wallets without multisigWallet) + const dRepId = multisigWallet ? multisigWallet.getDRepId() : appWallet?.dRepId; if (!dRepId) { setAlert("DRep not found"); toast({ diff --git a/src/components/pages/wallet/governance/card-info.tsx b/src/components/pages/wallet/governance/card-info.tsx index 13329ec..f371bb7 100644 --- a/src/components/pages/wallet/governance/card-info.tsx +++ b/src/components/pages/wallet/governance/card-info.tsx @@ -56,7 +56,9 @@ export default function CardInfo({ appWallet, manualUtxos }: { appWallet: Wallet } | null>(null); // Get DRep info for standard mode - const currentDrepId = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getDRepId() : appWallet?.dRepId; + // Use multisig wallet DRep ID if available (it handles no DRep keys by using payment script), + // otherwise fallback to appWallet (for legacy wallets without multisigWallet) + const currentDrepId = multisigWallet ? multisigWallet.getDRepId() : appWallet?.dRepId; const currentDrepInfo = drepInfo; diff --git a/src/components/pages/wallet/governance/drep/registerDrep.tsx b/src/components/pages/wallet/governance/drep/registerDrep.tsx index 11edfac..51b586c 100644 --- a/src/components/pages/wallet/governance/drep/registerDrep.tsx +++ b/src/components/pages/wallet/governance/drep/registerDrep.tsx @@ -114,21 +114,47 @@ export default function RegisterDRep({ onClose }: RegisterDRepProps = {}) { } async function registerDrep(): Promise { - if (!connected || !userAddress || !multisigWallet || !appWallet) - throw new Error("Multisig wallet not connected"); + if (!connected || !userAddress || !appWallet) + throw new Error("Wallet not connected"); setLoading(true); const txBuilder = getTxBuilder(network); - const drepData = multisigWallet?.getDRep(appWallet); - if (!drepData) { - throw new Error("DRep not found"); + // For legacy wallets (no multisigWallet), use appWallet values directly (preserves input order) + // For SDK wallets, use multisigWallet to compute DRep ID and script + let dRepId: string; + let drepCbor: string; + let scriptCbor: string; + let changeAddress: string; + + if (multisigWallet) { + const drepData = multisigWallet.getDRep(appWallet); + if (!drepData) { + throw new Error("DRep not found"); + } + dRepId = drepData.dRepId; + drepCbor = drepData.drepCbor; + const multisigScript = multisigWallet.getScript(); + const multisigScriptCbor = multisigScript.scriptCbor; + const appScriptCbor = appWallet.scriptCbor; + if (!multisigScriptCbor && !appScriptCbor) { + throw new Error("Script CBOR not found"); + } + scriptCbor = multisigWallet.getKeysByRole(3) ? (multisigScriptCbor || appScriptCbor!) : (appScriptCbor || multisigScriptCbor!); + changeAddress = multisigScript.address; + } else { + // Legacy wallet: use appWallet values (computed with input order preserved) + if (!appWallet.dRepId || !appWallet.scriptCbor) { + throw new Error("DRep ID or script not found for legacy wallet"); + } + dRepId = appWallet.dRepId; + drepCbor = appWallet.scriptCbor; // Use payment script CBOR for legacy wallets + scriptCbor = appWallet.scriptCbor; + changeAddress = appWallet.address; } - const { dRepId, drepCbor } = drepData; - const scriptCbor = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getScript().scriptCbor : appWallet.scriptCbor; - if (!scriptCbor) { - throw new Error("Script not found"); + if (!scriptCbor || !changeAddress) { + throw new Error("Script or change address not found"); } try { const { anchorUrl, anchorHash } = await createAnchor(); @@ -157,7 +183,7 @@ export default function RegisterDRep({ onClose }: RegisterDRepProps = {}) { anchorDataHash: anchorHash, }) .certificateScript(drepCbor) - .changeAddress(multisigWallet.getScript().address); + .changeAddress(changeAddress); diff --git a/src/components/pages/wallet/governance/proposal/voteButtton.tsx b/src/components/pages/wallet/governance/proposal/voteButtton.tsx index e1c9fa5..84a65a7 100644 --- a/src/components/pages/wallet/governance/proposal/voteButtton.tsx +++ b/src/components/pages/wallet/governance/proposal/voteButtton.tsx @@ -154,7 +154,12 @@ export default function VoteButton({ }; // Vote using proxy - const txBuilderResult = await proxyContract.voteProxyDrep([voteData], utxos, multisigWallet?.getScript().address); + // Use multisig wallet address if available, otherwise fallback to appWallet (for legacy wallets) + const proxyAddress = multisigWallet?.getScript().address || appWallet?.address; + if (!proxyAddress) { + throw new Error("Wallet address not found"); + } + const txBuilderResult = await proxyContract.voteProxyDrep([voteData], utxos, proxyAddress); await newTransaction({ txBuilder: txBuilderResult, @@ -229,9 +234,9 @@ export default function VoteButton({ setLoading(false); return; } - if (!multisigWallet) - throw new Error("Multisig Wallet could not be built."); - const dRepId = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getDRepId() : appWallet?.dRepId; + // Use multisig wallet DRep ID if available (it handles no DRep keys by using payment script), + // otherwise fallback to appWallet (for legacy wallets without multisigWallet) + const dRepId = multisigWallet ? multisigWallet.getDRepId() : appWallet?.dRepId; if (!dRepId) { setAlert("DRep not found"); toast({ diff --git a/src/components/pages/wallet/governance/proposals.tsx b/src/components/pages/wallet/governance/proposals.tsx index 4e57e00..a6e9c15 100644 --- a/src/components/pages/wallet/governance/proposals.tsx +++ b/src/components/pages/wallet/governance/proposals.tsx @@ -83,7 +83,9 @@ export default function AllProposals({ appWallet, utxos, selectedBallotId, onSel const order = "desc"; // Get DRep ID for fetching voting history (proxy mode or standard mode) - const standardDrepId = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getDRepId() : appWallet?.dRepId; + // Use multisig wallet DRep ID if available (it handles no DRep keys by using payment script), + // otherwise fallback to appWallet (for legacy wallets without multisigWallet) + const standardDrepId = multisigWallet ? multisigWallet.getDRepId() : appWallet?.dRepId; // Get proxy DRep ID if proxy is enabled useEffect(() => { diff --git a/src/components/pages/wallet/info/card-info.tsx b/src/components/pages/wallet/info/card-info.tsx index 8bbe3ae..88a9464 100644 --- a/src/components/pages/wallet/info/card-info.tsx +++ b/src/components/pages/wallet/info/card-info.tsx @@ -39,28 +39,44 @@ import Code from "@/components/ui/code"; import { Carousel } from "@/components/ui/carousel"; import { type MultisigWallet } from "@/utils/multisigSDK"; import { getFirstAndLast } from "@/utils/strings"; +import { getWalletType } from "@/utils/common"; export default function CardInfo({ appWallet }: { appWallet: Wallet }) { const [showEdit, setShowEdit] = useState(false); + + // Check if this is a legacy wallet using the centralized detection + const walletType = getWalletType(appWallet); + const isLegacyWallet = walletType === 'legacy'; return ( - - - - - setShowEdit(!showEdit)}> - {showEdit ? "Close Edit" : "Edit Wallet"} - - - +
+ {isLegacyWallet && ( + + + Legacy + + )} + + + + + + setShowEdit(!showEdit)}> + {showEdit ? "Close Edit" : "Edit Wallet"} + + + +
} cardClassName="col-span-2" > @@ -338,15 +354,17 @@ function ShowInfo({ appWallet }: { appWallet: Wallet }) { setBalance(balance); }, [appWallet, walletsUtxos]); - // Check if this is a legacy wallet (doesn't use SDK) - const isLegacyWallet = !!appWallet.rawImportBodies?.multisig; + // Check if this is a legacy wallet using the centralized detection + const walletType = getWalletType(appWallet); + const isLegacyWallet = walletType === 'legacy'; // For legacy wallets, multisigWallet will be undefined, so use appWallet.address // For SDK wallets, prefer the address from multisigWallet if staking is enabled const address = multisigWallet?.getKeysByRole(2) ? multisigWallet?.getScript().address : appWallet.address; - // Get DRep ID from multisig wallet if available, otherwise fallback to appWallet - const dRepId = multisigWallet?.getKeysByRole(3) ? multisigWallet?.getDRepId() : appWallet?.dRepId; + // Get DRep ID from multisig wallet if available (it handles no DRep keys by using payment script), + // otherwise fallback to appWallet (for legacy wallets without multisigWallet) + const dRepId = multisigWallet ? multisigWallet.getDRepId() : appWallet?.dRepId; // For rawImportBodies wallets, dRepId may not be available const showDRepId = dRepId && dRepId.length > 0; @@ -379,22 +397,6 @@ function ShowInfo({ appWallet }: { appWallet: Wallet }) { return (
- {/* Wallet Name */} - {appWallet.name && ( -
-
-
Wallet Name
- {isLegacyWallet && ( - - - Legacy - - )} -
-
{appWallet.name}
-
- )} - {/* Desktop: Grid layout for addresses and balance */}
{/* Left Column */} @@ -407,7 +409,7 @@ function ShowInfo({ appWallet }: { appWallet: Wallet }) { allowOverflow={false} /> - {/* Stake Address - Only show if staking is enabled */} + {/* Stake Address - Show if staking is enabled */} {multisigWallet && multisigWallet.stakingEnabled() && (() => { const stakeAddress = multisigWallet.getStakeAddress(); return stakeAddress ? ( @@ -420,6 +422,16 @@ function ShowInfo({ appWallet }: { appWallet: Wallet }) { ) : null; })()} + {/* External Stake Key Hash - Always show if available */} + {appWallet?.stakeCredentialHash && ( + + )} + {/* DRep ID */} {showDRepId && dRepId ? (
+ +
+