🛡️ About the Project: Multitrust – Secure Decisions, Signed Together

🚀 Inspiration

The idea for Multitrust was born out of a desire to improve collaborative decision-making on-chain. In traditional blockchain systems, single accounts hold disproportionate power, and multisig setups often feel rigid or underpowered. I wanted to build a system where multiple trusted parties could jointly authorize actions, with flexible thresholds and secure execution—without relying on external multisig wallets or off-chain coordination.

🧠 What I Learned

Building this pallet deepened my understanding of:

  • Substrate's storage primitives like StorageMap, StorageDoubleMap, and BoundedVec, which are essential for managing complex relationships between accounts and approvals.
  • Dispatchable calls and origin handling, especially how to securely execute runtime calls from a multisig context.
  • Hashing and encoding for call identification using blake2_256, ensuring uniqueness and integrity.
  • The importance of sorted data structures for deterministic account ID generation and preventing double voting.

I also explored how to use RawOrigin::Signed to simulate dispatch from a multisig account, and how to design events for transparency and traceability.

🛠️ How I Built It

The pallet was built using the Substrate framework with the following key components:

  • Account Registration: Users can register a multisig account by providing a list of signatories and a threshold. The pallet ensures the list is sorted and the sender is not duplicated.
  • Call Creation: A signatory can propose a call, which is hashed and stored with their initial approval.
  • Approval & Dispatch: Other signatories can approve the call. Once the threshold is met, the call is dispatched using the multisig account as origin.
  • Storage Design:
    • Account<T> stores signatories.
    • Threshold<T> stores required approvals.
    • Calls<T> tracks approvals per call hash.
    • Executed<T> marks dispatched calls.

The multisig account ID is derived using:

[ \text{AccountId} = \text{Decode}(\text{blake2_256}(b"modlpy/utilisuba", \text{signatories}, \text{threshold})) ]

This ensures deterministic and unique account generation.

⚠️ Challenges Faced

  • Storage Efficiency: Initially, signatories and thresholds were stored separately, which led to inefficient queries. A better design would use a unified struct or NStorageMap.
  • Approval Logic: Preventing double voting required careful use of sorted vectors and bounded types.
  • Call Dispatch: Ensuring that calls are only dispatched once, and handling errors gracefully, was tricky.
  • RPC Extensions: Designing custom RPCs to query call status, signatories, and thresholds is still pending and will require additional runtime interfaces.

🌱 Future Improvements

  • Implement call cancellation and unvoting logic.
  • Add RPCs for better frontend integration.
  • Optimize storage layout for fewer reads.
  • Support weighted voting or time-based approvals.

Built With

Share this project:

Updates

Submission history