diff --git a/src/components/pages/homepage/wallets/new-wallet-flow/create/index.tsx b/src/components/pages/homepage/wallets/new-wallet-flow/create/index.tsx index 0d6ab38e..68b0e44d 100644 --- a/src/components/pages/homepage/wallets/new-wallet-flow/create/index.tsx +++ b/src/components/pages/homepage/wallets/new-wallet-flow/create/index.tsx @@ -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(); @@ -70,35 +71,60 @@ export default function PageReviewWallet() { /> {/* Action Section - Warning and Create Button */} -
- {/* Warning Message */} -
- - - - {walletFlow.usesStored ? ( -

- Not yet compatible: This wallet was created with a stored script format that is not supported for creation here yet. Please check back soon. -

- ) : walletFlow.hasSignerHashInAddresses ? ( -

- Invite not completed: Not all signers visited the invite link. -

- ) : ( -

- Important: Creation is final - signers and rules can not be changed afterwards. -

- )} +
+
+ {/* Warning Message */} +
+ + + + {walletFlow.usesStored && !walletFlow.hasValidRawImportBodies ? ( +

+ Not yet compatible: This wallet was created with a stored script format that is not supported for creation here yet. Please check back soon. +

+ ) : walletFlow.hasSignerHashInAddresses ? ( +

+ Invite not completed: Not all signers visited the invite link. +

+ ) : ( +

+ Important: Creation is final - signers and rules can not be changed afterwards. +

+ )} +
+ {/* Create Button */} +
- {/* Create Button */} - + {/* Bypass option for hash signers */} + {walletFlow.hasSignerHashInAddresses && ( + + )}
)} diff --git a/src/components/pages/homepage/wallets/new-wallet-flow/ready/index.tsx b/src/components/pages/homepage/wallets/new-wallet-flow/ready/index.tsx index b6d0dec0..c93a30a7 100644 --- a/src/components/pages/homepage/wallets/new-wallet-flow/ready/index.tsx +++ b/src/components/pages/homepage/wallets/new-wallet-flow/ready/index.tsx @@ -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(); @@ -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); diff --git a/src/components/pages/homepage/wallets/new-wallet-flow/shared/useWalletFlowState.tsx b/src/components/pages/homepage/wallets/new-wallet-flow/shared/useWalletFlowState.tsx index 7314ddb0..fb393201 100644 --- a/src/components/pages/homepage/wallets/new-wallet-flow/shared/useWalletFlowState.tsx +++ b/src/components/pages/homepage/wallets/new-wallet-flow/shared/useWalletFlowState.tsx @@ -58,6 +58,11 @@ export interface WalletFlowState { isValidForSave: boolean; isValidForCreate: boolean; hasSignerHashInAddresses: boolean; + hasValidRawImportBodies: boolean; + + // Bypass options + allowCreateWithHashSigners: boolean; + setAllowCreateWithHashSigners: React.Dispatch>; // Router info router: ReturnType; @@ -107,6 +112,7 @@ export function useWalletFlowState(): WalletFlowState { const [loading, setLoading] = useState(false); const [nativeScriptType, setNativeScriptType] = useState<"all" | "any" | "atLeast">("atLeast"); const [stakeKey, setStakeKey] = useState(""); + const [allowCreateWithHashSigners, setAllowCreateWithHashSigners] = useState(false); // Dependencies const userAddress = useUserStore((state) => state.userAddress); @@ -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; @@ -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; } @@ -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 @@ -660,6 +680,11 @@ export function useWalletFlowState(): WalletFlowState { isValidForSave, isValidForCreate, hasSignerHashInAddresses, + hasValidRawImportBodies, + + // Bypass options + allowCreateWithHashSigners, + setAllowCreateWithHashSigners, // Router info router, diff --git a/src/components/pages/homepage/wallets/new-wallet/index.tsx b/src/components/pages/homepage/wallets/new-wallet/index.tsx index f14b1c15..ccdd062a 100644 --- a/src/components/pages/homepage/wallets/new-wallet/index.tsx +++ b/src/components/pages/homepage/wallets/new-wallet/index.tsx @@ -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"; @@ -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"); } @@ -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, }); diff --git a/src/components/pages/wallet/governance/ballot/ballot.tsx b/src/components/pages/wallet/governance/ballot/ballot.tsx index 3a983d04..ffcb793c 100644 --- a/src/components/pages/wallet/governance/ballot/ballot.tsx +++ b/src/components/pages/wallet/governance/ballot/ballot.tsx @@ -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 @@ -122,13 +125,9 @@ 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); @@ -136,12 +135,8 @@ export default function BallotCard({ // 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 @@ -486,7 +481,7 @@ export default function BallotCard({ )} {selectedBallot && ( <> - {isProxyEnabled && !selectedProxyId && ( + {isProxyEnabled && proxies && proxies.length > 0 && !selectedProxyId && (

Proxy Mode Active - Select a proxy to continue @@ -499,10 +494,10 @@ export default function BallotCard({

- {isProxyEnabled ? "Proxy DRep Information" : "DRep Information"} + {hasValidProxyData ? "Proxy DRep Information" : "DRep Information"}

Note: governance features are currently in alpha as Blockfrost and CIPs standards are work in progress. @@ -181,7 +180,7 @@ export default function CardInfo({ appWallet, manualUtxos }: { appWallet: Wallet

- {isProxyEnabled ? "No proxy DRep information available" : "No DRep information available"} + {hasValidProxyData ? "No proxy DRep information available" : "No DRep information available"}

)} @@ -202,7 +201,7 @@ export default function CardInfo({ appWallet, manualUtxos }: { appWallet: Wallet

- {isProxyEnabled ? "Proxy DRep Information" : "DRep Information"} + {hasValidProxyData ? "Proxy DRep Information" : "DRep Information"}

Note: governance features are currently in alpha as Blockfrost and CIPs standards are work in progress. @@ -241,84 +240,86 @@ export default function CardInfo({ appWallet, manualUtxos }: { appWallet: Wallet

- {isProxyEnabled ? "Proxy DRep Management" : "DRep Information"} + {hasValidProxyData ? "Proxy DRep Management" : "DRep Information"}

- {isProxyEnabled ? "Using proxy for governance operations" : "Standard DRep governance mode"} + {hasValidProxyData ? "Using proxy for governance operations" : "Standard DRep governance mode"}

- {/* Global Proxy Toggle */} -
- Proxy Mode: - -
- - {proxies.length > 0 && ( -
- - {selectedProxyId && ( - - )} -
+ + +
+ +
+ + {selectedProxyId && ( + + )} +
+ )}
diff --git a/src/components/pages/wallet/governance/drep/registerDrep.tsx b/src/components/pages/wallet/governance/drep/registerDrep.tsx index 03cd137b..9522dbde 100644 --- a/src/components/pages/wallet/governance/drep/registerDrep.tsx +++ b/src/components/pages/wallet/governance/drep/registerDrep.tsx @@ -44,6 +44,9 @@ export default function RegisterDRep() { { 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)); + const [manualUtxos, setManualUtxos] = useState([]); const [formState, setFormState] = useState({ givenName: "", @@ -171,8 +174,9 @@ export default function RegisterDRep() { throw new Error("Multisig wallet not connected"); } - if (!selectedProxyId) { - throw new Error("Please select a proxy for registration"); + if (!hasValidProxy) { + // Fall back to standard registration if no valid proxy + return registerDrep(); } setLoading(true); @@ -185,7 +189,8 @@ export default function RegisterDRep() { // Get the selected proxy const proxy = proxies?.find((p: any) => p.id === selectedProxyId); if (!proxy) { - throw new Error("Selected proxy not found"); + // Fall back to standard registration if proxy not found + return registerDrep(); } // Create proxy contract instance with the selected proxy @@ -226,22 +231,24 @@ export default function RegisterDRep() { Register DRep
- {/* Global Proxy Status */} -
-
- - {isProxyEnabled ? 'Proxy Mode Enabled' : 'Standard Mode'} - - - {isProxyEnabled - ? 'DRep will be registered using a proxy contract' - : 'DRep will be registered directly' - } - -
+ {/* Global Proxy Status - Only show when proxies exist */} + {proxies && proxies.length > 0 && ( +
+
+ + {isProxyEnabled ? 'Proxy Mode Enabled' : 'Standard Mode'} + + + {isProxyEnabled + ? 'DRep will be registered using a proxy contract' + : 'DRep will be registered directly' + } + +
+ )} - {/* Proxy Configuration */} - {isProxyEnabled && ( + {/* Proxy Configuration - Only show when proxies exist */} + {isProxyEnabled && proxies && proxies.length > 0 && (

This will register the DRep using a proxy contract, allowing for more flexible governance control. @@ -331,9 +338,9 @@ export default function RegisterDRep() { // This function is intentionally left empty. }} loading={loading} - onSubmit={isProxyEnabled ? registerProxyDrep : registerDrep} + onSubmit={hasValidProxy ? registerProxyDrep : registerDrep} mode="register" - isProxyMode={isProxyEnabled} + isProxyMode={hasValidProxy} /> )}

diff --git a/src/components/pages/wallet/governance/drep/retire.tsx b/src/components/pages/wallet/governance/drep/retire.tsx index bfe9ca29..5edde52c 100644 --- a/src/components/pages/wallet/governance/drep/retire.tsx +++ b/src/components/pages/wallet/governance/drep/retire.tsx @@ -37,6 +37,9 @@ export default function Retire({ appWallet, manualUtxos }: { appWallet: Wallet; { 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)); + // Helper function to get multisig inputs (like in register component) const getMsInputs = useCallback(async (): Promise<{ utxos: UTxO[]; walletAddress: string }> => { if (!multisigWallet?.getScript().address) { @@ -57,13 +60,9 @@ export default function Retire({ appWallet, manualUtxos }: { appWallet: Wallet; }); 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 retire if no valid proxy + return retireDrep(); } setLoading(true); @@ -72,12 +71,8 @@ export default function Retire({ appWallet, manualUtxos }: { appWallet: Wallet; // 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 retire if proxy not found + return retireDrep(); } // Get multisig inputs @@ -249,12 +244,12 @@ export default function Retire({ appWallet, manualUtxos }: { appWallet: Wallet;
- {isProxyEnabled && !selectedProxyId && ( + {isProxyEnabled && proxies && proxies.length > 0 && !selectedProxyId && (

Proxy Mode Active - Select a proxy to continue diff --git a/src/components/pages/wallet/governance/drep/updateDrep.tsx b/src/components/pages/wallet/governance/drep/updateDrep.tsx index 2b117ef7..7904d9c2 100644 --- a/src/components/pages/wallet/governance/drep/updateDrep.tsx +++ b/src/components/pages/wallet/governance/drep/updateDrep.tsx @@ -45,6 +45,9 @@ export default function UpdateDRep() { { 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)); + // Helper function to get multisig inputs (like in register component) const getMsInputs = useCallback(async (): Promise<{ utxos: UTxO[]; walletAddress: string }> => { if (!multisigWallet?.getScript().address) { @@ -108,8 +111,9 @@ export default function UpdateDRep() { if (!connected || !userAddress || !multisigWallet || !appWallet) { throw new Error("Multisig wallet not connected"); } - if (!isProxyEnabled || !selectedProxyId) { - throw new Error("Proxy mode not enabled or no proxy selected"); + if (!hasValidProxy) { + // Fall back to standard update if no valid proxy + return updateDrep(); } setLoading(true); @@ -118,7 +122,8 @@ export default function UpdateDRep() { // Get the selected proxy const proxy = proxies?.find((p: any) => p.id === selectedProxyId); if (!proxy) { - throw new Error("Selected proxy not found"); + // Fall back to standard update if proxy not found + return updateDrep(); } // Create anchor metadata @@ -274,9 +279,9 @@ export default function UpdateDRep() { // This function is intentionally left empty. }} loading={loading} - onSubmit={isProxyEnabled ? updateProxyDrep : updateDrep} + onSubmit={hasValidProxy ? updateProxyDrep : updateDrep} mode="update" - isProxyMode={isProxyEnabled} + isProxyMode={hasValidProxy} /> )}

diff --git a/src/components/pages/wallet/governance/proposal/voteButtton.tsx b/src/components/pages/wallet/governance/proposal/voteButtton.tsx index 5f5a72e3..8c99ab2c 100644 --- a/src/components/pages/wallet/governance/proposal/voteButtton.tsx +++ b/src/components/pages/wallet/governance/proposal/voteButtton.tsx @@ -87,14 +87,13 @@ export default function VoteButton({ { 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)); + async function voteProxy() { - 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 vote(); } setLoading(true); @@ -102,12 +101,8 @@ export default function VoteButton({ // 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 vote(); } // Create proxy contract instance @@ -125,14 +120,14 @@ export default function VoteButton({ ); proxyContract.proxyAddress = proxy.proxyAddress; - // Prepare vote - const vote = { + // Prepare vote data + const voteData = { proposalId, voteKind: voteKind, }; // Vote using proxy - const txBuilderResult = await proxyContract.voteProxyDrep([vote], utxos, multisigWallet?.getScript().address); + const txBuilderResult = await proxyContract.voteProxyDrep([voteData], utxos, multisigWallet?.getScript().address); await newTransaction({ txBuilder: txBuilderResult, @@ -386,7 +381,7 @@ export default function VoteButton({ - {isProxyEnabled && !selectedProxyId && ( + {isProxyEnabled && proxies && proxies.length > 0 && !selectedProxyId && (

Proxy Mode Active - Select a proxy to continue @@ -398,11 +393,11 @@ export default function VoteButton({ )} {selectedBallotId && ( diff --git a/src/components/pages/wallet/info/card-info.tsx b/src/components/pages/wallet/info/card-info.tsx index 4c0203ad..39f1491a 100644 --- a/src/components/pages/wallet/info/card-info.tsx +++ b/src/components/pages/wallet/info/card-info.tsx @@ -173,25 +173,36 @@ function EditInfo({ function ShowInfo({ appWallet }: { appWallet: Wallet }) { const { multisigWallet } = useMultisigWallet(); + + 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; - if (!dRepId) { - throw new Error("DRep not found"); - } + + // For rawImportBodies wallets, dRepId may not be available + const hasRawImportBodies = !!appWallet.rawImportBodies?.multisig; + const showDRepId = dRepId && dRepId.length > 0; return ( <> - + {showDRepId ? ( + + ) : hasRawImportBodies ? ( + + ) : null} ); } diff --git a/src/components/pages/wallet/staking/StakingActions/stake.tsx b/src/components/pages/wallet/staking/StakingActions/stake.tsx index 64d6d8a9..36ac6bc3 100644 --- a/src/components/pages/wallet/staking/StakingActions/stake.tsx +++ b/src/components/pages/wallet/staking/StakingActions/stake.tsx @@ -38,7 +38,9 @@ export default function StakeButton({ const rewardAddress = mWallet.getStakeAddress(); if (!rewardAddress) throw new Error("Reward Address could not be built."); - const stakingScript = mWallet.getStakingScript(); + // For wallets with rawImportBodies, use stored stake script + // Otherwise, derive from MultisigWallet + const stakingScript = appWallet.stakeScriptCbor || mWallet.getStakingScript(); if (!stakingScript) throw new Error("Staking Script could not be built."); const txBuilder = getTxBuilder(network); diff --git a/src/hooks/common.ts b/src/hooks/common.ts index f377d42a..8cdf67cc 100644 --- a/src/hooks/common.ts +++ b/src/hooks/common.ts @@ -1,5 +1,4 @@ -import { Wallet as DbWallet } from "@prisma/client"; -import { Wallet } from "@/types/wallet"; +import { DbWalletWithLegacy, Wallet } from "@/types/wallet"; import { NativeScript, resolveNativeScriptHash, @@ -14,9 +13,15 @@ import { Address, getDRepIds } from "@meshsdk/core-cst"; import { MultisigKey, MultisigWallet } from "@/utils/multisigSDK"; export function buildMultisigWallet( - wallet: DbWallet, + wallet: DbWalletWithLegacy, network: number, ): MultisigWallet | undefined { + // For wallets with rawImportBodies, skip MultisigWallet building + // These wallets use a build process not supported by our SDK + if (wallet.rawImportBodies?.multisig) { + return undefined; + } + const keys: MultisigKey[] = []; if (wallet.signersAddresses.length > 0) { wallet.signersAddresses.forEach((addr, i) => { @@ -79,19 +84,64 @@ export function buildMultisigWallet( } export function buildWallet( - wallet: DbWallet, + wallet: DbWalletWithLegacy, network: number, utxos?: UTxO[], ): Wallet { + // For wallets with rawImportBodies, use stored values instead of deriving + if (wallet.rawImportBodies?.multisig) { + const multisig = wallet.rawImportBodies.multisig; + + // Always use stored address from rawImportBodies + const address = multisig.address; + if (!address) { + throw new Error("rawImportBodies.multisig.address is required"); + } + + // Always use stored payment script from rawImportBodies + const scriptCbor = multisig.payment_script; + if (!scriptCbor) { + throw new Error("rawImportBodies.multisig.payment_script is required"); + } + + // Extract stake script from rawImportBodies + const stakeScriptCbor = multisig.stake_script; + + // For rawImportBodies wallets, we need a minimal nativeScript for type compatibility + // This won't be used for actual script derivation, but is required by the Wallet type + const scriptType = (wallet.type as "all" | "any" | "atLeast") ?? "atLeast"; + const nativeScript: NativeScript = scriptType === "atLeast" + ? { + type: "atLeast", + required: wallet.numRequiredSigners ?? 1, + scripts: [], + } + : { + type: scriptType, + scripts: [], + }; + + // For rawImportBodies wallets, dRepId cannot be easily derived from stored CBOR + // Set to empty string - it can be derived later if needed from the actual script + const dRepId = ""; + + return { + ...wallet, + scriptCbor, + nativeScript, + address, + dRepId, + stakeScriptCbor, + } as Wallet; + } + + // For wallets without rawImportBodies, use existing derivation logic const mWallet = buildMultisigWallet(wallet, network); if (!mWallet) { console.error("error when building Multisig Wallet!"); throw new Error("Failed to build Multisig Wallet"); } - //depricated -> only payment-script left in for compatibility - //uses unordered keys for payment script - //Remove later when refactoring const nativeScript = { type: wallet.type ? wallet.type : "atLeast", scripts: wallet.signersAddresses.map((addr) => ({ @@ -117,8 +167,7 @@ export function buildWallet( const paymentAddrEmpty = utxos?.filter((f) => f.output.address === paymentAddress).length === 0; - if (paymentAddrEmpty && mWallet.stakingEnabled()) address = stakeableAddress - + if (paymentAddrEmpty && mWallet.stakingEnabled()) address = stakeableAddress; const dRepIdCip105 = resolveScriptHashDRepId( resolveNativeScriptHash(nativeScript as NativeScript), diff --git a/src/hooks/useAppWallet.ts b/src/hooks/useAppWallet.ts index daa30352..4450b4e7 100644 --- a/src/hooks/useAppWallet.ts +++ b/src/hooks/useAppWallet.ts @@ -4,6 +4,7 @@ import { buildWallet } from "./common"; import { useSiteStore } from "@/lib/zustand/site"; import { useRouter } from "next/router"; import { useWalletsStore } from "@/lib/zustand/wallets"; +import { DbWalletWithLegacy } from "@/types/wallet"; export default function useAppWallet() { const router = useRouter(); @@ -21,7 +22,7 @@ export default function useAppWallet() { ); if (wallet) { - return { appWallet: buildWallet(wallet, network, walletsUtxos[walletId]), isLoading }; + return { appWallet: buildWallet(wallet as DbWalletWithLegacy, network, walletsUtxos[walletId]), isLoading }; } return { appWallet: undefined, isLoading }; diff --git a/src/hooks/useMultisigWallet.ts b/src/hooks/useMultisigWallet.ts index 3cf7c16a..e8fe63c3 100644 --- a/src/hooks/useMultisigWallet.ts +++ b/src/hooks/useMultisigWallet.ts @@ -4,6 +4,7 @@ import { api } from "@/utils/api"; import { useSiteStore } from "@/lib/zustand/site"; import { useUserStore } from "@/lib/zustand/user"; import { buildMultisigWallet } from "./common"; +import { DbWalletWithLegacy } from "@/types/wallet"; export default function useMultisigWallet() { const router = useRouter(); @@ -19,7 +20,7 @@ export default function useMultisigWallet() { }, ); if (wallet) { - return { multisigWallet: buildMultisigWallet(wallet, network), wallet, isLoading }; + return { multisigWallet: buildMultisigWallet(wallet as DbWalletWithLegacy, network), wallet, isLoading }; } return { multisigWallet: undefined, wallet: undefined, isLoading }; diff --git a/src/hooks/useUserWallets.ts b/src/hooks/useUserWallets.ts index eaae3723..1f5aff9c 100644 --- a/src/hooks/useUserWallets.ts +++ b/src/hooks/useUserWallets.ts @@ -2,6 +2,7 @@ import { useUserStore } from "@/lib/zustand/user"; import { useSiteStore } from "@/lib/zustand/site"; import { api } from "@/utils/api"; import { buildWallet } from "./common"; +import { DbWalletWithLegacy } from "@/types/wallet"; export default function useUserWallets() { const network = useSiteStore((state) => state.network); @@ -17,7 +18,7 @@ export default function useUserWallets() { if (wallets) { _wallets = wallets.map((wallet) => { - return buildWallet(wallet, network); + return buildWallet(wallet as DbWalletWithLegacy, network); }); return { wallets: _wallets, isLoading }; } diff --git a/src/pages/api/v1/freeUtxos.ts b/src/pages/api/v1/freeUtxos.ts index f619f81a..06cc2c5a 100644 --- a/src/pages/api/v1/freeUtxos.ts +++ b/src/pages/api/v1/freeUtxos.ts @@ -11,6 +11,7 @@ import type { UTxO } from "@meshsdk/core"; import { createCaller } from "@/server/api/root"; import { db } from "@/server/db"; import { verifyJwt } from "@/lib/verifyJwt"; +import { DbWalletWithLegacy } from "@/types/wallet"; export default async function handler( req: NextApiRequest, @@ -75,7 +76,7 @@ export default async function handler( if (!walletFetch) { return res.status(404).json({ error: "Wallet not found" }); } - const mWallet = buildMultisigWallet(walletFetch); + const mWallet = buildMultisigWallet(walletFetch as DbWalletWithLegacy); if (!mWallet) { return res.status(500).json({ error: "Wallet could not be constructed" }); } diff --git a/src/pages/api/v1/nativeScript.ts b/src/pages/api/v1/nativeScript.ts index fe0eb443..2e610bc4 100644 --- a/src/pages/api/v1/nativeScript.ts +++ b/src/pages/api/v1/nativeScript.ts @@ -5,6 +5,7 @@ import { buildMultisigWallet } from "@/utils/common"; import { verifyJwt } from "@/lib/verifyJwt"; import { createCaller } from "@/server/api/root"; import { db } from "@/server/db"; +import { DbWalletWithLegacy } from "@/types/wallet"; export default async function handler( req: NextApiRequest, @@ -57,7 +58,7 @@ export default async function handler( if (!walletFetch) { return res.status(404).json({ error: "Wallet not found" }); } - const mWallet = buildMultisigWallet(walletFetch); + const mWallet = buildMultisigWallet(walletFetch as DbWalletWithLegacy); if (!mWallet) { return res.status(500).json({ error: "Wallet could not be constructed" }); } diff --git a/src/pages/api/v1/stats/run-snapshots-batch.ts b/src/pages/api/v1/stats/run-snapshots-batch.ts index 8a2f249f..942695b1 100644 --- a/src/pages/api/v1/stats/run-snapshots-batch.ts +++ b/src/pages/api/v1/stats/run-snapshots-batch.ts @@ -9,6 +9,7 @@ import { getBalance } from "@/utils/getBalance"; import { addressToNetwork } from "@/utils/multisigSDK"; import type { Wallet as DbWallet } from "@prisma/client"; import { Decimal } from "@prisma/client/runtime/library"; +import { DbWalletWithLegacy } from "@/types/wallet"; interface WalletBalance { walletId: string; @@ -305,7 +306,7 @@ export default async function handler( walletAddress = mWallet.getScript().address; } else { // Fallback: build the wallet without enforcing key ordering (legacy payment-script build) - const builtWallet = buildWallet(wallet, network); + const builtWallet = buildWallet(wallet as DbWalletWithLegacy, network); walletAddress = builtWallet.address; } } catch (error) { diff --git a/src/server/api/routers/wallets.ts b/src/server/api/routers/wallets.ts index cb04392f..856136a0 100644 --- a/src/server/api/routers/wallets.ts +++ b/src/server/api/routers/wallets.ts @@ -37,32 +37,55 @@ export const walletRouter = createTRPCRouter({ description: z.string(), signersAddresses: z.array(z.string()), signersDescriptions: z.array(z.string()), - signersStakeKeys: z.array(z.string()), - signersDRepKeys: z.array(z.string()), + signersStakeKeys: z.array(z.string().nullable()).nullable(), + signersDRepKeys: z.array(z.string().optional()).nullable(), numRequiredSigners: z.number(), scriptCbor: z.string(), stakeCredentialHash: z.string().optional(), type: z.enum(["atLeast", "all", "any"]), - rawImportBodies: z.custom().optional().nullable(), + rawImportBodies: z.any().optional().nullable(), }), ) .mutation(async ({ ctx, input }) => { - const numRequired = (input.type === "all" || input.type === "any") ? null : input.numRequiredSigners; - const data = { - name: input.name, - description: input.description, - signersAddresses: input.signersAddresses, - signersDescriptions: input.signersDescriptions, - signersStakeKeys: input.signersStakeKeys, - signersDRepKeys: input.signersDRepKeys, - numRequiredSigners: numRequired as any, - scriptCbor: input.scriptCbor, - stakeCredentialHash: input.stakeCredentialHash, - type: input.type, - rawImportBodies: input.rawImportBodies as Prisma.InputJsonValue, - } as unknown as Prisma.WalletCreateInput; + try { + const numRequired = (input.type === "all" || input.type === "any") ? null : input.numRequiredSigners; + + // Convert null/undefined values to empty strings to match Prisma schema + // Keep array length to match signersAddresses + const signersStakeKeys = (input.signersStakeKeys || []).map(key => + key === null || key === undefined ? "" : key + ); + const signersDRepKeys = (input.signersDRepKeys || []).map(key => + key === null || key === undefined ? "" : key + ); + + // Ensure rawImportBodies is properly serialized if present + let rawImportBodiesValue: Prisma.InputJsonValue | undefined = undefined; + if (input.rawImportBodies) { + // If it's already a plain object, use it directly + // Otherwise, serialize it to ensure it's JSON-compatible + rawImportBodiesValue = JSON.parse(JSON.stringify(input.rawImportBodies)) as Prisma.InputJsonValue; + } + + const data: Prisma.WalletCreateInput = { + name: input.name, + description: input.description, + signersAddresses: input.signersAddresses, + signersDescriptions: input.signersDescriptions, + signersStakeKeys: signersStakeKeys, + signersDRepKeys: signersDRepKeys, + numRequiredSigners: numRequired as any, + scriptCbor: input.scriptCbor, + stakeCredentialHash: input.stakeCredentialHash, + type: input.type, + rawImportBodies: rawImportBodiesValue, + }; - return ctx.db.wallet.create({ data }); + return ctx.db.wallet.create({ data }); + } catch (error) { + console.error("Error creating wallet:", error); + throw error; + } }), updateWalletVerifiedList: publicProcedure diff --git a/src/types/wallet.ts b/src/types/wallet.ts index 6e2e1274..11a74f5d 100644 --- a/src/types/wallet.ts +++ b/src/types/wallet.ts @@ -1,12 +1,6 @@ import { NativeScript } from "@meshsdk/core"; import { Wallet as DbWallet } from "@prisma/client"; -export type Wallet = DbWallet & { - nativeScript: NativeScript; - address: string; - dRepId: string; -}; - export interface RawImportBodiesUser { ada_handle?: string; address_bech32?: string; @@ -45,3 +39,14 @@ export interface RawImportBodies { [key: string]: unknown; } +export type DbWalletWithLegacy = DbWallet & { + rawImportBodies?: RawImportBodies | null; +}; + +export type Wallet = DbWalletWithLegacy & { + nativeScript: NativeScript; + address: string; + dRepId: string; + stakeScriptCbor?: string; +}; + diff --git a/src/utils/common.ts b/src/utils/common.ts index 79499342..887615aa 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,5 +1,4 @@ -import { Wallet as DbWallet } from "@prisma/client"; -import { Wallet } from "@/types/wallet"; +import { DbWalletWithLegacy, Wallet } from "@/types/wallet"; import { NativeScript, resolveNativeScriptHash, @@ -18,9 +17,15 @@ function addressToNetwork(address: string): number { } export function buildMultisigWallet( - wallet: DbWallet, + wallet: DbWalletWithLegacy, network?: number, ): MultisigWallet | undefined { + // For wallets with rawImportBodies, skip MultisigWallet building + // These wallets use a build process not supported by our SDK + if (wallet.rawImportBodies?.multisig) { + return undefined; + } + console.log( "buildMultisigWallet - stakeCredentialHash", wallet.stakeCredentialHash, @@ -81,11 +86,58 @@ export function buildMultisigWallet( } export function buildWallet( - wallet: DbWallet, + wallet: DbWalletWithLegacy, network: number, utxos?: UTxO[], ): Wallet { - + // For wallets with rawImportBodies, use stored values instead of deriving + if (wallet.rawImportBodies?.multisig) { + const multisig = wallet.rawImportBodies.multisig; + + // Always use stored address from rawImportBodies + const address = multisig.address; + if (!address) { + throw new Error("rawImportBodies.multisig.address is required"); + } + + // Always use stored payment script from rawImportBodies + const scriptCbor = multisig.payment_script; + if (!scriptCbor) { + throw new Error("rawImportBodies.multisig.payment_script is required"); + } + + // Extract stake script from rawImportBodies + const stakeScriptCbor = multisig.stake_script; + + // For rawImportBodies wallets, we need a minimal nativeScript for type compatibility + // This won't be used for actual script derivation, but is required by the Wallet type + const scriptType = (wallet.type as "all" | "any" | "atLeast") ?? "atLeast"; + const nativeScript: NativeScript = scriptType === "atLeast" + ? { + type: "atLeast", + required: wallet.numRequiredSigners ?? 1, + scripts: [], + } + : { + type: scriptType, + scripts: [], + }; + + // For rawImportBodies wallets, dRepId cannot be easily derived from stored CBOR + // Set to empty string - it can be derived later if needed from the actual script + const dRepId = ""; + + return { + ...wallet, + scriptCbor, + nativeScript, + address, + dRepId, + stakeScriptCbor, + } as Wallet; + } + + // For wallets without rawImportBodies, use existing derivation logic const mWallet = buildMultisigWallet(wallet, network); if (!mWallet) { console.error("error when building Multisig Wallet!");