Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CollapsibleAdvancedSection from "@/components/pages/homepage/wallets/new-
import { Button } from "@/components/ui/button";
import WalletFlowPageLayout from "@/components/pages/homepage/wallets/new-wallet-flow/shared/WalletFlowPageLayout";
import { useWalletFlowState } from "@/components/pages/homepage/wallets/new-wallet-flow/shared/useWalletFlowState";
import { Check } from "lucide-react";

export default function PageReviewWallet() {
const walletFlow = useWalletFlowState();
Expand Down Expand Up @@ -70,35 +71,60 @@ export default function PageReviewWallet() {
/>

{/* Action Section - Warning and Create Button */}
<div className="mt-6 sm:mt-8 flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:justify-between">
{/* Warning Message */}
<div className="flex items-start gap-2 p-4 bg-muted/50 rounded-lg w-fit">
<svg className="w-5 h-5 mt-0.5 flex-shrink-0 text-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
{walletFlow.usesStored ? (
<p className="text-sm text-foreground">
<strong>Not yet compatible:</strong> This wallet was created with a stored script format that is not supported for creation here yet. Please check back soon.
</p>
) : walletFlow.hasSignerHashInAddresses ? (
<p className="text-sm text-foreground">
<strong>Invite not completed:</strong> Not all signers visited the invite link.
</p>
) : (
<p className="text-sm text-foreground">
<strong>Important:</strong> Creation is final - signers and rules can not be changed afterwards.
</p>
)}
<div className="mt-6 sm:mt-8 flex flex-col gap-4">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:justify-between">
{/* Warning Message */}
<div className="flex items-start gap-2 p-4 bg-muted/50 rounded-lg w-fit">
<svg className="w-5 h-5 mt-0.5 flex-shrink-0 text-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
{walletFlow.usesStored && !walletFlow.hasValidRawImportBodies ? (
<p className="text-sm text-foreground">
<strong>Not yet compatible:</strong> This wallet was created with a stored script format that is not supported for creation here yet. Please check back soon.
</p>
) : walletFlow.hasSignerHashInAddresses ? (
<p className="text-sm text-foreground">
<strong>Invite not completed:</strong> Not all signers visited the invite link.
</p>
) : (
<p className="text-sm text-foreground">
<strong>Important:</strong> Creation is final - signers and rules can not be changed afterwards.
</p>
)}
</div>
{/* Create Button */}
<Button
onClick={walletFlow.createNativeScript}
disabled={!walletFlow.isValidForCreate || (walletFlow.usesStored && !walletFlow.hasValidRawImportBodies)}
className="w-full sm:w-auto"
size="lg"
>
{walletFlow.loading ? "Creating..." : "Create"}
</Button>
</div>
{/* Create Button */}
<Button
onClick={walletFlow.createNativeScript}
disabled={!walletFlow.isValidForCreate || walletFlow.usesStored}
className="w-full sm:w-auto"
size="lg"
>
{walletFlow.loading ? "Creating..." : "Create"}
</Button>
{/* Bypass option for hash signers */}
{walletFlow.hasSignerHashInAddresses && (
<button
type="button"
onClick={() => {
walletFlow.setAllowCreateWithHashSigners(!walletFlow.allowCreateWithHashSigners);
}}
className="flex items-center space-x-2 p-4 bg-muted/30 rounded-lg hover:bg-muted/50 transition-colors w-full text-left"
>
<div className={`flex items-center justify-center h-5 w-5 rounded border-2 transition-colors ${
walletFlow.allowCreateWithHashSigners
? "bg-primary border-primary"
: "border-muted-foreground/50"
}`}>
{walletFlow.allowCreateWithHashSigners && (
<Check className="h-4 w-4 text-primary-foreground" />
)}
</div>
<span className="text-sm font-normal select-none flex-1">
I understand that not all signers have visited the invite link. Create wallet anyway.
</span>
</button>
)}
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Button } from "@/components/ui/button";
import ProgressIndicator from "@/components/pages/homepage/wallets/new-wallet-flow/shared/ProgressIndicator";
import WalletFlowPageLayout from "@/components/pages/homepage/wallets/new-wallet-flow/shared/WalletFlowPageLayout";
import { buildMultisigWallet } from "@/utils/common";
import { DbWalletWithLegacy } from "@/types/wallet";

export default function PageSuccessWallet() {
const router = useRouter();
Expand All @@ -36,7 +37,7 @@ export default function PageSuccessWallet() {
);

// Build wallet with address and other computed fields
const wallet = walletData ? buildMultisigWallet(walletData, network) : null;
const wallet = walletData ? buildMultisigWallet(walletData as DbWalletWithLegacy, network) : null;

const handleViewWallets = () => {
setLoading(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export interface WalletFlowState {
isValidForSave: boolean;
isValidForCreate: boolean;
hasSignerHashInAddresses: boolean;
hasValidRawImportBodies: boolean;

// Bypass options
allowCreateWithHashSigners: boolean;
setAllowCreateWithHashSigners: React.Dispatch<React.SetStateAction<boolean>>;

// Router info
router: ReturnType<typeof useRouter>;
Expand Down Expand Up @@ -107,6 +112,7 @@ export function useWalletFlowState(): WalletFlowState {
const [loading, setLoading] = useState<boolean>(false);
const [nativeScriptType, setNativeScriptType] = useState<"all" | "any" | "atLeast">("atLeast");
const [stakeKey, setStakeKey] = useState<string>("");
const [allowCreateWithHashSigners, setAllowCreateWithHashSigners] = useState<boolean>(false);

// Dependencies
const userAddress = useUserStore((state) => state.userAddress);
Expand Down Expand Up @@ -394,11 +400,6 @@ export function useWalletFlowState(): WalletFlowState {
function createNativeScript() {
setLoading(true);

if (!multisigWallet) {
setLoading(false);
throw new Error("Multisig wallet could not be built.");
}

// Prefer imported payment CBOR from walletInvite when available
type WalletInviteExtras = {
paymentCbor?: string;
Expand All @@ -407,11 +408,21 @@ export function useWalletFlowState(): WalletFlowState {
};
const inviteExtras = (walletInvite as unknown as WalletInviteExtras) || {};
const importedPaymentCbor = inviteExtras.paymentCbor;
const hasRawImportBodies = !!inviteExtras.rawImportBodies?.multisig;

let scriptCborToUse: string | undefined;

if (importedPaymentCbor && importedPaymentCbor.length > 0) {
// For wallets with rawImportBodies, use stored payment script
if (hasRawImportBodies && inviteExtras.rawImportBodies?.multisig?.payment_script) {
scriptCborToUse = inviteExtras.rawImportBodies.multisig.payment_script;
} else if (importedPaymentCbor && importedPaymentCbor.length > 0) {
scriptCborToUse = importedPaymentCbor;
} else {
// For regular wallets, require multisigWallet to derive script
if (!multisigWallet) {
setLoading(false);
throw new Error("Multisig wallet could not be built.");
}
const { scriptCbor } = multisigWallet.getScript();
scriptCborToUse = scriptCbor;
}
Expand Down Expand Up @@ -612,12 +623,21 @@ export function useWalletFlowState(): WalletFlowState {
});
}, [signersAddresses]);

// Check if we have rawImportBodies with required data
const hasValidRawImportBodies = useMemo(() => {
if (!walletInvite) return false;
const inviteExtras = (walletInvite as any) || {};
return !!inviteExtras.rawImportBodies?.multisig?.payment_script;
}, [walletInvite]);

const isValidForCreate = signersAddresses.length > 0 &&
!signersAddresses.some((signer) => !signer || signer.length === 0) &&
(nativeScriptType !== "atLeast" || numRequiredSigners > 0) &&
name.length > 0 &&
!loading &&
!hasSignerHashInAddresses;
(!hasSignerHashInAddresses || allowCreateWithHashSigners) &&
// Allow creation if we have valid rawImportBodies, otherwise require multisigWallet
(hasValidRawImportBodies || !!multisigWallet);

return {
// Core wallet data
Expand Down Expand Up @@ -660,6 +680,11 @@ export function useWalletFlowState(): WalletFlowState {
isValidForSave,
isValidForCreate,
hasSignerHashInAddresses,
hasValidRawImportBodies,

// Bypass options
allowCreateWithHashSigners,
setAllowCreateWithHashSigners,

// Router info
router,
Expand Down
35 changes: 29 additions & 6 deletions src/components/pages/homepage/wallets/new-wallet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useUserStore } from "@/lib/zustand/user";
import { useSiteStore } from "@/lib/zustand/site";
import { useToast } from "@/hooks/use-toast";
import useUser from "@/hooks/useUser";
import type { RawImportBodies } from "@/types/wallet";

import PageHeader from "@/components/common/page-header";
import WalletInfoCard from "@/components/pages/homepage/wallets/new-wallet/nWInfoCard";
Expand Down Expand Up @@ -189,13 +190,34 @@ export default function PageNewWallet() {
async function createNativeScript() {
setLoading(true);

if (!multisigWallet) {
setLoading(false);
throw new Error("Multisig wallet could not be built.");
// Check for rawImportBodies from wallet invite
type WalletInviteExtras = {
paymentCbor?: string;
stakeCbor?: string | null;
rawImportBodies?: RawImportBodies | null;
};
const inviteExtras = (walletInvite as unknown as WalletInviteExtras) || {};
const importedPaymentCbor = inviteExtras.paymentCbor;
const hasRawImportBodies = !!inviteExtras.rawImportBodies?.multisig;

let scriptCborToUse: string | undefined;

// For wallets with rawImportBodies, use stored payment script
if (hasRawImportBodies && inviteExtras.rawImportBodies?.multisig?.payment_script) {
scriptCborToUse = inviteExtras.rawImportBodies.multisig.payment_script;
} else if (importedPaymentCbor && importedPaymentCbor.length > 0) {
scriptCborToUse = importedPaymentCbor;
} else {
// For regular wallets, require multisigWallet to derive script
if (!multisigWallet) {
setLoading(false);
throw new Error("Multisig wallet could not be built.");
}
const { scriptCbor } = multisigWallet.getScript();
scriptCborToUse = scriptCbor;
}

const { scriptCbor } = multisigWallet.getScript();
if (!scriptCbor) {
if (!scriptCborToUse) {
setLoading(false);
throw new Error("scriptCbor is undefined");
}
Expand All @@ -208,7 +230,8 @@ export default function PageNewWallet() {
signersStakeKeys: signersStakeKeys,
signersDRepKeys: signersDRepKeys,
numRequiredSigners: numRequiredSigners,
scriptCbor: scriptCbor,
scriptCbor: scriptCborToUse,
rawImportBodies: inviteExtras.rawImportBodies ?? null,
stakeCredentialHash: stakeKey.length > 0 ? stakeKey : undefined,
type: nativeScriptType,
});
Expand Down
29 changes: 12 additions & 17 deletions src/components/pages/wallet/governance/ballot/ballot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export default function BallotCard({
{ enabled: !!(appWallet?.id || userAddress) }
);

// Check if we have valid proxy data (proxy enabled, selected, proxies exist, and selected proxy is found)
const hasValidProxy = !!(isProxyEnabled && selectedProxyId && proxies && proxies.length > 0 && proxies.find((p: any) => p.id === selectedProxyId));

// CreateBallot mutation
const createBallot = api.ballot.create.useMutation();
// Get ballots for wallet
Expand Down Expand Up @@ -122,26 +125,18 @@ export default function BallotCard({
});
return;
}
if (!isProxyEnabled || !selectedProxyId) {
toast({
title: "Proxy Error",
description: "Proxy mode not enabled or no proxy selected",
variant: "destructive",
});
return;
if (!hasValidProxy) {
// Fall back to standard vote if no valid proxy
return handleSubmitVote();
}

setLoading(true);
try {
// Get the selected proxy
const proxy = proxies?.find((p: any) => p.id === selectedProxyId);
if (!proxy) {
toast({
title: "Proxy Error",
description: "Selected proxy not found",
variant: "destructive",
});
return;
// Fall back to standard vote if proxy not found
return handleSubmitVote();
}

// Create proxy contract instance
Expand Down Expand Up @@ -486,7 +481,7 @@ export default function BallotCard({
)}
{selectedBallot && (
<>
{isProxyEnabled && !selectedProxyId && (
{isProxyEnabled && proxies && proxies.length > 0 && !selectedProxyId && (
<div className="mt-2 p-2 bg-yellow-50 dark:bg-yellow-950/20 border border-yellow-200 dark:border-yellow-800 rounded-md">
<p className="text-xs text-yellow-800 dark:text-yellow-200 font-medium">
Proxy Mode Active - Select a proxy to continue
Expand All @@ -499,10 +494,10 @@ export default function BallotCard({
<Button
variant="default"
className="mt-4"
onClick={isProxyEnabled ? handleSubmitProxyVote : handleSubmitVote}
disabled={loading || (isProxyEnabled && !selectedProxyId)}
onClick={hasValidProxy ? handleSubmitProxyVote : handleSubmitVote}
disabled={loading}
>
{loading ? "Loading..." : `Submit Ballot Vote${isProxyEnabled ? " (Proxy Mode)" : ""}`}
{loading ? "Loading..." : `Submit Ballot Vote${hasValidProxy ? " (Proxy Mode)" : ""}`}
</Button>
<Button
variant="destructive"
Expand Down
Loading