🛡️ 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, andBoundedVec, 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
- polkadot
- rust
Log in or sign up for Devpost to join the conversation.