Sui.

Post

Share your knowledge.

SuiLover.
Jul 30, 2025
Expert Q&A

How do i implement a multi sig wallet in Sui Move?


I'm trying to understand this aspect of the Sui Network because I'm either building, debugging, or deploying something that touches this area. I want a detailed explanation of how this mechanism or feature works, along with relevant CLI usage, Move code structure, or architectural concepts. My goal is to gain enough clarity to apply this knowledge in a real project—whether that's a custom smart contract, an NFT system, a wallet integration, or a DeFi tool. The Sui Network has unique features compared to EVM chains, so I'm particularly interested in what sets it apart and how that affects development best practices. It would help to have sample code, command line examples, or typical errors to watch for, especially when using the Sui CLI, SDK, or deploying on localnet/testnet. Ultimately, I want to avoid common mistakes, follow the best security principles, and ensure that the functionality I’m working on behaves as expected under realistic conditions.

  • Sui
  • Move
0
11
Share
Comments
.

Answers

11
Paul.
Paul4340
Jul 31 2025, 05:44

To implement a multi-signature (multi-sig) wallet in Sui Move, you need to design a contract where multiple signers must approve an action (e.g., transferring funds) before it can be executed. This involves tracking signatures from multiple addresses and verifying that a predefined threshold of approvals is met.

Key Concepts:

  1. Threshold: Define how many signatures (from different addresses) are required to authorize a transaction.
  2. Signers: Track the addresses that have signed the transaction.
  3. Transaction Execution: Only execute the transaction once the required number of signatures is reached.

Example Code:

module MultiSigWallet {
    use sui::object::{Object, move_to};

    struct MultiSig {
        signers: vector<address>,
        threshold: u64,
        signed: vector<address>,
    }

    public fun create_wallet(signers: vector<address>, threshold: u64): MultiSig {
        assert!(signers.len() >= threshold, 0);  // Ensure enough signers
        MultiSig {
            signers,
            threshold,
            signed: Vector::empty<address>(),
        }
    }

    public fun sign_transaction(wallet: &mut MultiSig, signer: address) {
        assert!(Vector::contains(&wallet.signers, signer), 1);  // Check if signer is valid
        assert!(!Vector::contains(&wallet.signed, signer), 2);  // Ensure signer hasn't signed already
        Vector::push_back(&mut wallet.signed, signer);
    }

    public fun execute_transaction(wallet: &mut MultiSig) {
        assert!(Vector::length(&wallet.signed) >= wallet.threshold, 3);  // Check if threshold is met
        // Execute the action (e.g., transfer funds)
    }
}

CLI Usage:

  1. Create Multi-Sig Wallet:

    sui client call --function create_wallet --package <package-id> --module MultiSigWallet --args <signer-1> <signer-2> <threshold>
    
  2. Sign Transaction:

    sui client call --function sign_transaction --package <package-id> --module MultiSigWallet --args <wallet-id> <signer-1>
    
  3. Execute Transaction:

    sui client call --function execute_transaction --package <package-id> --module MultiSigWallet --args <wallet-id>
    

Best Practices:

  • Ensure signer addresses are securely managed to prevent unauthorized access.
  • Test on localnet/testnet to avoid issues on Mainnet.
  • Validate that only the required number of signatures are needed for execution.
7
Comments
.
Benjamin XDV.
Jul 31 2025, 09:50

To implement a multi-signature wallet in Sui Move, create a shared object that stores approval states from multiple signers and requires a threshold of signatures for transactions. Use vector<address> to track authorized signers and a u8 threshold field to enforce the minimum required approvals. Unlike EVM's Gnosis Safe-style contracts, Sui's model leverages object ownership—the wallet should be a shared object (key + store) with custom entry functions that validate signatures via tx_context::sender(). Key considerations include preventing replay attacks with nonces and ensuring atomic execution via programmable transaction blocks for multi-step operations.

7
Comments
.
Ashford.
Jul 31 2025, 06:43

Implementing a multi-signature (multi-sig) wallet on the Sui Network using Move is a great way to enhance the security of transactions by requiring multiple signatures before an action can be executed. A multi-sig wallet ensures that several participants (or keys) must approve a transaction, which is especially useful in decentralized governance, shared wallets, or enterprise-level applications.

Key Concepts:

  1. Multi-Sig Logic: In a multi-sig wallet, there are multiple authorized addresses (signers). A transaction is only valid if a specified threshold of signers approve it.
  2. Ownership and Permissions: Each signer must have the permission to sign transactions. This can be tracked using a set of signers and a threshold in the Move smart contract.
  3. Transaction Approval: A transaction is executed once enough signers have signed off on it.

Steps to Implement a Multi-Sig Wallet:

  1. Define Signers and Threshold:

    • Store a list of signers (addresses) and define a threshold (number of signatures required to approve a transaction).
  2. Track Signatures:

    • Use a structure to track who has signed the transaction.
  3. Execute Transaction:

    • Only allow the transaction to proceed once the required number of signatures has been collected.

Sample Code for a Multi-Sig Wallet in Move:

module MultiSigWallet {

    use 0x1::Coin;

    struct MultiSigWallet has store {
        signers: vector<address>,  // List of signers' addresses
        threshold: u8,  // Minimum number of signatures required
        signatures: vector<address>,  // List of addresses that have signed the transaction
    }

    // Initialize a new Multi-Sig Wallet
    public fun initialize(signers: vector<address>, threshold: u8): MultiSigWallet {
        assert(threshold <= length(&signers), 0); // Ensure threshold is valid
        MultiSigWallet {
            signers,
            threshold,
            signatures: Vector::empty<address>(),
        }
    }

    // Function to add a signature (one signer approves the transaction)
    public fun add_signature(wallet: &mut MultiSigWallet, signer: address) {
        // Ensure the signer is part of the authorized signers
        let signer_index = Vector::index_of(&wallet.signers, signer);
        assert(signer_index != Option::none<u8>(), 0);

        // Ensure the signer hasn't already signed
        let already_signed = Vector::index_of(&wallet.signatures, signer);
        assert(already_signed == Option::none<u8>(), 0);

        // Add the signer to the list of signatures
        Vector::push_back(&mut wallet.signatures, signer);
    }

    // Check if the transaction can be executed (threshold of signatures reached)
    public fun can_execute(wallet: &MultiSigWallet): bool {
        length(&wallet.signatures) >= wallet.threshold
    }

    // Function to execute the transaction if enough signatures are collected
    public fun execute_transaction(wallet: &mut MultiSigWallet, tx_data: &str) {
        if (can_execute(wallet)) {
            // Execute the transaction (e.g., transfer funds, etc.)
            // This is a placeholder for your transaction logic
            Coin::transfer(tx_data);
            // Reset signatures after execution
            wallet.signatures = Vector::empty<address>();
        } else {
            abort 1;  // Abort if not enough signatures
        }
    }
}

Explanation:

  1. MultiSigWallet Structure:

    • The MultiSigWallet stores the list of signers, the signature threshold, and the list of collected signatures.
  2. initialize Function:

    • Initializes a new wallet with the list of signers and the signature threshold.
  3. add_signature Function:

    • Allows a signer to add their signature to the wallet. It checks if the signer is authorized and ensures they haven’t already signed.
  4. can_execute Function:

    • Checks if the required number of signatures have been collected (based on the threshold).
  5. execute_transaction Function:

    • Executes the transaction if enough signatures are gathered. It could include custom logic like transferring coins or interacting with other contracts.

CLI Usage:

To deploy the contract and interact with it, you would typically use the Sui CLI to publish and call functions.

  1. Deploy the Contract:

    sui client publish --gas-budget 10000 --module path_to_module
    
  2. Initialize the Wallet: Deploy a new multi-sig wallet with a list of signers and a threshold for the number of required signatures:

    sui client call --gas-budget 10000 --function initialize --args <signer-addresses> <threshold> --network testnet
    
  3. Add Signatures: Each signer can add their signature by calling the add_signature function:

    sui client call --gas-budget 10000 --function add_signature --args <wallet-id> <signer-address> --network testnet
    
  4. Execute Transaction: Once the required signatures are collected, the transaction can be executed:

    sui client call --gas-budget 10000 --function execute_transaction --args <wallet-id> <tx-data> --network testnet
    

Common Errors:

  1. Invalid Signer: If an unauthorized address tries to sign, it will be rejected.
  2. Threshold Not Met: The transaction will fail if the threshold of signatures is not met.
  3. Multiple Signatures by Same User: The contract ensures each signer can only sign once. Trying to sign multiple times will result in an error.

Best Practices:

  • Limit Signer Access: Ensure only trusted addresses are included as signers. The integrity of the multi-sig wallet relies on who can sign.
  • Test Thoroughly: Always test your multi-sig logic on the Testnet before deploying it on the Mainnet to avoid issues in real-world usage.
  • Gas Estimation: Ensure that the gas budget is sufficient to cover all operations, especially when interacting with multiple signers.

Summary:

A multi-sig wallet in Move on the Sui Network requires careful design to handle multiple signers and ensure that a transaction is only executed when a sufficient number of signers approve it. By using vectors to store signers and signatures, you can easily implement a robust multi-sig wallet that enhances the security of transactions. Use the provided Move code and Sui CLI commands to deploy and interact with the multi-sig wallet in your Sui-based applications.

6
Comments
.
Alya.
Alya-14
Jul 30 2025, 17:33

To implement a multisig wallet in Sui Move:

  1. Store signers' addresses and required threshold in a MultiSig struct.
  2. Track signed transactions using a unique ID and a VecMap of signers.
  3. Require multiple approvals before execution—only when ≥ threshold signers have approved, execute via transfer::transfer or Coin::join.
  4. Use owned objects for the multisig state; do not share.
  5. Emit events (TransactionExecuted, Approved) for off-chain tracking.

Example:

struct MultiSig has key {
    id: UID,
    owners: vector<address>,
    threshold: u64,
}

Approve and execute in two steps. Use tx_context::sender() for signer validation, not signer type.

Leverage Sui’s programmable transaction blocks (PTBs) to batch approvals and executions efficiently. See Working with PTBs for batching patterns.

Never hold funds in a non-coin object—store balances as Coin<T> and transfer only when quorum is reached.

Best practice: Integrate with 0x2::clock for time-locked transactions if needed.

4
Comments
.
Arnold.
Arnold3036
Jul 31 2025, 08:32

Implement a multi-sig wallet in Move using a shared object with signer validation:

1. Core Structure

module multisig::wallet {
    use sui::object::{Self, UID};
    use sui::tx_context::TxContext;
    use sui::transfer;

    struct MultiSigWallet has key {
        id: UID,
        threshold: u8,
        owners: vector<address>,
        approved: vector<address> // Track approvals
    }
}

2. Approval Logic

public entry fun approve(
    wallet: &mut MultiSigWallet,
    signer: &signer,
    tx_hash: vector<u8>
) {
    let owner = tx_context::sender(signer);
    assert!(vector::contains(&wallet.owners, &owner), 0);
    vector::push_back(&mut wallet.approved, owner);
}

3. Execution

public entry fun execute(
    wallet: &mut MultiSigWallet,
    signers: vector<signer>,
    tx: vector<u8>
) {
    assert!(vector::length(&wallet.approved) >= wallet.threshold, 1);
    // Execute transaction logic here
    vector::clear(&mut wallet.approved); // Reset approvals
}

Key Features

  • Shared Object: MultiSigWallet is globally mutable
  • Threshold Security: Requires N/M approvals
  • Sui Advantage: Parallel approval collection (unlike EVM's sequential checks)

CLI Usage

sui client call \
    --function approve \
    --module wallet \
    --package 0xMULTISIG_PACKAGE \
    --args 0xWALLET_ID 0xTX_HASH \
    --gas-budget 10000000

Best Practices

  1. Use entry functions for all public interactions
  2. Store approved TX hashes to prevent replay attacks
  3. Test with sui move test for edge cases
4
Comments
.
Evgeniy CRYPTOCOIN.
Jul 31 2025, 09:26

To implement a multi-sig wallet in Sui Move:

  1. Define Signers – Store approved signer addresses in a vector<address>.
  2. Track Approvals – Use a Table<address, bool> to record votes.
  3. Threshold Check – Execute only after N signatures.

Key Move Features:

sui::tx_context::sender() for signer auth.
shared object for concurrent access.

Example Logic:

public entry fun approve(tx: &mut MultiSigTx, ctx: &TxContext) {  
    let sender = tx_context::sender(ctx);  
    assert!(signers.contains(sender), ENotSigner);  
    votes.insert(sender, true);  
    if (votes.length() >= threshold) execute(tx);  
}  

Watch For:

  • Replay attacks (use nonces).
  • Storage costs for large signer lists.

(Sui’s object model simplifies multi-sig vs EVM’s complex proxy wallets.)

4
Comments
.
Bekky.
Bekky1762
Jul 31 2025, 12:23

Here's a concise implementation:

module multi_sig::wallet {
    use sui::object::{UID};
    use sui::tx_context::TxContext;
    use sui::vec_set;

    struct MultiSig has key {
        id: UID,
        threshold: u8,
        owners: vector<address>,
        pending: vector<PendingTx>
    }

    struct PendingTx has store {
        data: vector<u8>,
        approvals: vec_set::VecSet<address>
    }

    public fun create(
        owners: vector<address>,
        threshold: u8,
        ctx: &mut TxContext
    ): MultiSig {
        MultiSig {
            id: object::new(ctx),
            threshold,
            owners,
            pending: vector::empty()
        }
    }

    public entry fun submit(
        wallet: &mut MultiSig,
        data: vector<u8>,
        ctx: &mut TxContext
    ) {
        vector::push_back(&mut wallet.pending, PendingTx {
            data,
            approvals: vec_set::empty()
        });
    }

    public entry fun approve(
        wallet: &mut MultiSig,
        tx_index: u64,
        ctx: &TxContext
    ) {
        let tx = vector::borrow_mut(&mut wallet.pending, tx_index);
        vec_set::insert(&mut tx.approvals, tx_context::sender(ctx));
    }
}

Key CLI Commands:

  1. Create wallet:

    sui client call --package <PACKAGE> --module wallet --function create \
      --args "[0x1,0x2,0x3]" 2 --gas-budget 10000
    
  2. Submit tx:

    sui client call --package <PACKAGE> --module wallet --function submit \
      --args <WALLET_ID> "68656c6c6f" --gas-budget 10000
    
  3. Approve tx:

    sui client call --package <PACKAGE> --module wallet --function approve \
      --args <WALLET_ID> 0 --gas-budget 10000
    

Key Differences from EVM:

  • Uses Sui's object model instead of storage slots
  • Each operation is a distinct Move call
  • Parallel execution possible

For production use, add execution logic and more robust access controls.

3
Comments
.
theking.
Jul 30 2025, 10:46

To implement a multi-signature (multisig) wallet in Sui Move, you need to define a contract that collects multiple user approvals before executing sensitive operations. Unlike Ethereum where multisig is typically achieved via a smart contract that checks msg.sender, Sui handles ownership differently using object capability-based access control, so you must explicitly track signers and approvals within a Move struct.

You start by creating a MultisigWallet object that holds a list of addresses (public keys) and a threshold value. When someone submits a transaction, your module should store the proposed transaction (as a hash or struct), and then allow authorized signers to call a sign function. Once enough unique approvals are gathered (based on the threshold), you allow the transaction to be executed using an execute entry function. This ensures that actions only proceed when enough stakeholders agree.

Sui Move supports vector<address>, making it suitable for keeping a dynamic signer list. However, you must implement logic to avoid double-signing and ensure only authorized addresses can approve. You should also consider security features like replay protection by hashing the action and nonce together.

Here’s a simplified skeleton in Move:

module multisig::wallet {
    use sui::object::{Self, UID};
    use std::vector;
    use std::address;
    use std::hash;
    use std::option;

    struct MultisigWallet has key {
        id: UID,
        owners: vector<address>,
        threshold: u8,
        nonce: u64,
    }

    public fun init_wallet(owners: vector<address>, threshold: u8, ctx: &mut TxContext): MultisigWallet {
        assert!(vector::length(&owners) >= threshold, 0);
        MultisigWallet {
            id: object::new(ctx),
            owners,
            threshold,
            nonce: 0
        }
    }

    public entry fun propose_action(wallet: &mut MultisigWallet, action_hash: vector<u8>, ctx: &mut TxContext) {
        // Store proposed action, associate nonce
    }

    public entry fun sign_action(wallet: &mut MultisigWallet, signer: address, action_hash: vector<u8>) {
        // Track signers and count approvals
    }

    public entry fun execute_action(wallet: &mut MultisigWallet, action_hash: vector<u8>, ctx: &mut TxContext) {
        // If enough signatures collected, perform operation
    }
}

To call this from the Sui TypeScript SDK:

const tx = new TransactionBlock();
tx.moveCall({
  target: `${packageId}::wallet::execute_action`,
  arguments: [
    tx.object(walletId),
    tx.pure(actionHash)
  ]
});
await signer.signAndExecuteTransactionBlock({ transactionBlock: tx });

The biggest difference from EVM chains is that in Sui, smart contracts don’t read global state implicitly—every object must be passed explicitly, and each access needs proper permissions. You also don’t have msg.sender; you get the signer from the TxContext, so multisig must be enforced manually.

To avoid issues in production, you should:

  • Validate signer identities carefully.
  • Protect against double-signing or replays using nonces.
  • Break up logic into small steps to reduce conflict chances.
  • Simulate your logic locally using sui move test or dryRunTransactionBlock.

Read more about building secure and composable Sui contracts here: https://docs.sui.io/build/move And refer to the multisig discussion in Sui community dev examples: https://github.com/MystenLabs/sui

1
Comments
.

Do you know the answer?

Please log in and share it.