diff --git a/src/components/pages/homepage/wallets/new-wallet-flow/create/ReviewRequiredSignersCard.tsx b/src/components/pages/homepage/wallets/new-wallet-flow/create/ReviewRequiredSignersCard.tsx index 3c433a00..39cc9188 100644 --- a/src/components/pages/homepage/wallets/new-wallet-flow/create/ReviewRequiredSignersCard.tsx +++ b/src/components/pages/homepage/wallets/new-wallet-flow/create/ReviewRequiredSignersCard.tsx @@ -166,6 +166,26 @@ const ReviewRequiredSignersCard: React.FC = ({ })()} + + {/* Info message for "all" and "any" script types */} + {(nativeScriptType === 'all' || nativeScriptType === 'any') && ( +
+ + + +
+

+ {nativeScriptType === 'all' ? 'All Signers Required' : 'Any Signer Can Approve'} +

+

+ With "{nativeScriptType === 'all' ? 'All' : 'Any'}" script type, no specific number is stored. + {nativeScriptType === 'all' + ? ' Every signer must approve each transaction.' + : ' Any single signer can approve each transaction.'} +

+
+
+ )} {/* Edit button - only show for atLeast type */} {nativeScriptType === "atLeast" && (
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 089ada23..f245345b 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 @@ -158,6 +158,7 @@ export function useWalletFlowState(): WalletFlowState { numRequiredSigners, network, stakeKey || undefined, + nativeScriptType, ); }, [ name, @@ -168,6 +169,7 @@ export function useWalletFlowState(): WalletFlowState { numRequiredSigners, network, stakeKey, + nativeScriptType, ]); // API Mutations diff --git a/src/components/pages/wallet/info/signers/card-signers.tsx b/src/components/pages/wallet/info/signers/card-signers.tsx index 938e5ac7..6e5abf5c 100644 --- a/src/components/pages/wallet/info/signers/card-signers.tsx +++ b/src/components/pages/wallet/info/signers/card-signers.tsx @@ -20,9 +20,30 @@ export default function CardSigners({ appWallet }: { appWallet: Wallet }) { title="Signers" description={ <> - This wallet requires{" "} - {appWallet.numRequiredSigners} signers{" "} - to sign a transaction. + {(() => { + const signersCount = appWallet.signersAddresses.length; + if (appWallet.type === 'all') { + return ( + <> + All signers (of {signersCount}) must approve each transaction. + + ); + } else if (appWallet.type === 'any') { + return ( + <> + Any signer (of {signersCount}) can approve each transaction. + + ); + } else { + return ( + <> + This wallet requires{" "} + {appWallet.numRequiredSigners} of {signersCount} signers{" "} + to sign a transaction. + + ); + } + })()} } headerDom={ diff --git a/src/hooks/common.ts b/src/hooks/common.ts index 817d8539..d7efe6a7 100644 --- a/src/hooks/common.ts +++ b/src/hooks/common.ts @@ -58,6 +58,7 @@ export function buildMultisigWallet( wallet.numRequiredSigners ?? 1, network, wallet.stakeCredentialHash ?? undefined, + (wallet.type as "all" | "any" | "atLeast") ?? "atLeast", ); return multisigWallet; } diff --git a/src/server/api/routers/wallets.ts b/src/server/api/routers/wallets.ts index 137a7c76..bb62f664 100644 --- a/src/server/api/routers/wallets.ts +++ b/src/server/api/routers/wallets.ts @@ -43,6 +43,7 @@ export const walletRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { + const numRequired = (input.type === "all" || input.type === "any") ? null : input.numRequiredSigners; return ctx.db.wallet.create({ data: { name: input.name, @@ -50,7 +51,7 @@ export const walletRouter = createTRPCRouter({ signersAddresses: input.signersAddresses, signersDescriptions: input.signersDescriptions, signersStakeKeys: input.signersStakeKeys, - numRequiredSigners: input.numRequiredSigners, + numRequiredSigners: numRequired as any, scriptCbor: input.scriptCbor, stakeCredentialHash: input.stakeCredentialHash, type: input.type, @@ -168,6 +169,7 @@ export const walletRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { + const numRequired = (input.scriptType === "all" || input.scriptType === "any") ? null : input.numRequiredSigners; return ctx.db.newWallet.create({ data: { name: input.name, @@ -175,7 +177,7 @@ export const walletRouter = createTRPCRouter({ signersAddresses: input.signersAddresses, signersDescriptions: input.signersDescriptions, signersStakeKeys: input.signersStakeKeys, - numRequiredSigners: input.numRequiredSigners, + numRequiredSigners: numRequired as any, ownerAddress: input.ownerAddress, stakeCredentialHash: input.stakeCredentialHash, scriptType: input.scriptType, @@ -198,6 +200,7 @@ export const walletRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { + const numRequired = (input.scriptType === "all" || input.scriptType === "any") ? null : input.numRequiredSigners; return ctx.db.newWallet.update({ where: { id: input.walletId, @@ -208,7 +211,7 @@ export const walletRouter = createTRPCRouter({ signersAddresses: input.signersAddresses, signersDescriptions: input.signersDescriptions, signersStakeKeys: input.signersStakeKeys, - numRequiredSigners: input.numRequiredSigners, + numRequiredSigners: numRequired as any, stakeCredentialHash: input.stakeCredentialHash, scriptType: input.scriptType, } as any, diff --git a/src/utils/multisigSDK.ts b/src/utils/multisigSDK.ts index f4199c71..d259c926 100644 --- a/src/utils/multisigSDK.ts +++ b/src/utils/multisigSDK.ts @@ -144,6 +144,8 @@ export class MultisigWallet { network: number; /** Optional external stake credential hash (28-byte hex string) */ stakeCredentialHash: string | undefined; + /** Script type: "all", "any", or "atLeast" */ + type: "all" | "any" | "atLeast"; /** * Creates a new MultisigWallet instance. @@ -154,6 +156,7 @@ export class MultisigWallet { * @param required - Number of signatures required (default: 1) * @param network - Network identifier: 0=testnet, 1=mainnet (default: 1) * @param stakeCredentialHash - Optional external stake credential hash (28-byte hex string) + * @param type - Script type: "all", "any", or "atLeast" (default: "atLeast") * * @throws {Error} If no valid payment keys are provided * @@ -175,7 +178,8 @@ export class MultisigWallet { * "Wallet with staking capabilities", * 2, // require 2 signatures * 0, // testnet - * "external_stake_credential_hash" + * "external_stake_credential_hash", + * "all" // all signers must approve * ); * ``` */ @@ -186,6 +190,7 @@ export class MultisigWallet { required?: number, network?: number, stakeCredentialHash?: string, + type: "all" | "any" | "atLeast" = "atLeast", ) { this.name = name; // Filter out any keys that are not valid @@ -200,6 +205,7 @@ export class MultisigWallet { this.required = required ? required : 1; this.network = network !== undefined ? network : 1; this.stakeCredentialHash = stakeCredentialHash; + this.type = type; } /** @@ -339,7 +345,7 @@ export class MultisigWallet { const filteredKeys = this.getKeysByRole(role); if (!filteredKeys) return undefined; // Build the script using only the keys of the specified role - return buildNativeScript(filteredKeys, this.required); + return buildNativeScript(filteredKeys, this.required, this.type); } /**