Arbitrum Stylus logo

Stylus by Example

MultiSig Wallet

An Arbitrum Stylus version implementation of Solidity MultiSig wallet.

The wallet owners can

  • submit a transaction
  • approve and revoke approval of pending transactions
  • anyone can execute a transaction after enough owners has approved it

Here is the interface for MultiSig wallet.

1interface IMultiSig {
2    function numConfirmationsRequired() external view returns (uint256);
3
4    function deposit() external payable;
5
6    function submitTransaction(address to, uint256 value, bytes calldata data) external;
7
8    function initialize(address[] memory owners, uint256 num_confirmations_required) external;
9
10    function executeTransaction(uint256 tx_index) external;
11
12    function confirmTransaction(uint256 tx_index) external;
13
14    function revokeConfirmation(uint256 tx_index) external;
15
16    function isOwner(address check_address) external view returns (bool);
17
18    function getTransactionCount() external view returns (uint256);
19
20    error AlreadyInitialized();
21
22    error ZeroOwners();
23
24    error InvaildConfirmationNumber();
25
26    error InvalidOwner();
27
28    error OwnerNotUnique();
29
30    error NotOwner();
31
32    error TxDoesNotExist();
33
34    error TxAlreadyExecuted();
35
36    error TxAlreadyConfirmed();
37
38    error TxNotConfirmed();
39
40    error ConfirmationNumberNotEnough();
41
42    error ExecuteFailed();
43}
1interface IMultiSig {
2    function numConfirmationsRequired() external view returns (uint256);
3
4    function deposit() external payable;
5
6    function submitTransaction(address to, uint256 value, bytes calldata data) external;
7
8    function initialize(address[] memory owners, uint256 num_confirmations_required) external;
9
10    function executeTransaction(uint256 tx_index) external;
11
12    function confirmTransaction(uint256 tx_index) external;
13
14    function revokeConfirmation(uint256 tx_index) external;
15
16    function isOwner(address check_address) external view returns (bool);
17
18    function getTransactionCount() external view returns (uint256);
19
20    error AlreadyInitialized();
21
22    error ZeroOwners();
23
24    error InvaildConfirmationNumber();
25
26    error InvalidOwner();
27
28    error OwnerNotUnique();
29
30    error NotOwner();
31
32    error TxDoesNotExist();
33
34    error TxAlreadyExecuted();
35
36    error TxAlreadyConfirmed();
37
38    error TxNotConfirmed();
39
40    error ConfirmationNumberNotEnough();
41
42    error ExecuteFailed();
43}

Example implementation of a MultiSig Wallet contract written in Rust.

src/lib.rs

1// Allow `cargo stylus export-abi` to generate a main function.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5/// Use an efficient WASM allocator.
6#[global_allocator]
7static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
8
9/// Import items from the SDK. The prelude contains common traits and macros.
10use stylus_sdk::{contract, evm, msg, prelude::*, call::{Call, call}, alloy_primitives::{Address, U256}, abi::Bytes};
11use alloy_sol_types::sol;
12
13// Define some events using the Solidity ABI.
14sol! {
15    event Deposit(address indexed sender, uint256 amount, uint256 balance);
16    event SubmitTransaction(
17        address indexed owner,
18        uint256 indexed txIndex,
19        address indexed to,
20        uint256 value,
21        bytes data
22    );
23    event ConfirmTransaction(address indexed owner, uint256 indexed txIndex);
24    event RevokeConfirmation(address indexed owner, uint256 indexed txIndex);
25    event ExecuteTransaction(address indexed owner, uint256 indexed txIndex);
26
27    // Error types for the MultiSig contract
28    error AlreadyInitialized();
29    error ZeroOwners(); // The owners number is 0 when init.
30    error InvaildConfirmationNumber();
31    error InvalidOwner(); // The owner address is invalid when init.
32    error OwnerNotUnique(); // The owner address is not unique when init.
33    error NotOwner(); // The sender is not an owner.
34    error TxDoesNotExist();
35    error TxAlreadyExecuted();
36    error TxAlreadyConfirmed();
37    error TxNotConfirmed();
38    error ConfirmationNumberNotEnough();
39    error ExecuteFailed();
40}
41
42// Define some persistent storage using the Solidity ABI.
43// `MultiSig` will be the entrypoint.
44sol_storage! {
45    #[entrypoint]
46    pub struct MultiSig {
47        address[] owners; // The addresses of the owners
48        mapping(address => bool) is_owner; // mapping from owner => bool
49        uint256 num_confirmations_required; // The number of confirmations required to execute a transaction
50        TxStruct[] transactions; // The transactions array
51        // mapping from tx index => owner => bool
52        mapping(uint256 => mapping(address => bool)) is_confirmed;
53    }
54
55    // Define the `TxStruct` struct
56    pub struct TxStruct {
57        address to;
58        uint256 value;
59        bytes data;
60        bool executed; // Whether the transaction has been executed
61        uint256 num_confirmations; // The number of confirmations of the current transaction
62    }
63}
64
65// Error types for the MultiSig contract
66#[derive(SolidityError)]
67pub enum MultiSigError {
68    AlreadyInitialized(AlreadyInitialized),
69    ZeroOwners(ZeroOwners),
70    InvaildConfirmationNumber(InvaildConfirmationNumber),
71    InvalidOwner(InvalidOwner),
72    OwnerNotUnique(OwnerNotUnique),
73    NotOwner(NotOwner),
74    TxDoesNotExist(TxDoesNotExist),
75    TxAlreadyExecuted(TxAlreadyExecuted),
76    TxAlreadyConfirmed(TxAlreadyConfirmed),
77    TxNotConfirmed(TxNotConfirmed),
78    ConfirmationNumberNotEnough(ConfirmationNumberNotEnough),
79    ExecuteFailed(ExecuteFailed),
80}
81
82/// Declare that `MultiSig` is a contract with the following external methods.
83#[external]
84impl MultiSig {
85    pub fn num_confirmations_required(&self) -> Result<U256, MultiSigError> {
86        Ok(self.num_confirmations_required.get())
87    }
88
89    // The `deposit` method is payable, so it can receive funds.
90    #[payable]
91    pub fn deposit(&mut self) {
92        let sender = msg::sender();
93        let amount = msg::value();
94        evm::log(
95            Deposit{
96                sender: sender, 
97                amount: amount, 
98                balance: contract::balance()
99            });
100    }
101
102    // The `submit_transaction` method submits a new transaction to the contract.
103    pub fn submit_transaction(&mut self, to: Address, value: U256, data: Bytes) -> Result<(), MultiSigError> {
104        // The sender must be an owner.
105        if !self.is_owner.get(msg::sender()) {
106            return Err(MultiSigError::NotOwner(NotOwner{}));
107        }
108
109        let tx_index = U256::from(self.transactions.len());
110        
111        // Add the transaction to the transactions array.
112        let mut new_tx = self.transactions.grow();
113        new_tx.to.set(to);
114        new_tx.value.set(value);
115        new_tx.data.set_bytes(data.clone());
116        new_tx.executed.set(false);
117        new_tx.num_confirmations.set(U256::from(0));
118
119        // Emit the `SubmitTransaction` event.
120        evm::log(SubmitTransaction {
121            owner: msg::sender(),
122            txIndex: tx_index,
123            to: to,
124            value: value,
125            data: data.to_vec(),
126        });
127        Ok(())
128    }
129
130
131    // The `initialize` method initializes the contract with the owners and the number of confirmations required.
132    pub fn initialize(&mut self, owners: Vec<Address>, num_confirmations_required: U256) -> Result<(), MultiSigError> {
133        // The owners must not be initialized.
134        if self.owners.len() > 0 {
135            return Err(MultiSigError::AlreadyInitialized(AlreadyInitialized{}));
136        }
137
138        // The owners must not be empty.
139        if owners.len() == 0 {
140            return Err(MultiSigError::ZeroOwners(ZeroOwners{}));
141        }
142
143        // The number of confirmations required must be greater than 0 and less than or equal to the number of owners.
144        if num_confirmations_required == U256::from(0) || num_confirmations_required > U256::from(owners.len()) {
145            return Err(MultiSigError::InvaildConfirmationNumber(InvaildConfirmationNumber{}));
146        }
147
148        // Add the owners to the contract.
149        for owner in owners.iter() {
150            if *owner == Address::default() {
151                return Err(MultiSigError::InvalidOwner(InvalidOwner{}))
152            }
153
154            if self.is_owner.get(*owner) {
155                return Err(MultiSigError::OwnerNotUnique(OwnerNotUnique{}))
156            }
157
158            self.is_owner.setter(*owner).set(true);
159            self.owners.push(*owner);
160        }
161
162        // Set the number of confirmations required.
163        self.num_confirmations_required.set(num_confirmations_required);
164        Ok(())
165    }
166
167    // The `execute_transaction` method executes a transaction.
168    pub fn execute_transaction(&mut self, tx_index: U256) -> Result<(), MultiSigError>{
169        // The sender must be an owner.
170        if !self.is_owner.get(msg::sender()) {
171            return Err(MultiSigError::NotOwner(NotOwner{}));
172        }
173
174        // The transaction must exist.
175        let tx_index = tx_index.to::<usize>();
176        if tx_index >= self.transactions.len() {
177            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
178        }
179
180        // Try get transaction and check transaction is valid or not, if valid, execute it, if not, revert tx.
181        if let Some(mut entry) = self.transactions.get_mut(tx_index) {
182            if entry.executed.get() {
183                return Err(MultiSigError::TxAlreadyExecuted(TxAlreadyExecuted{}));
184            }
185
186            if entry.num_confirmations.get() < self.num_confirmations_required.get() {
187                return Err(MultiSigError::ConfirmationNumberNotEnough(ConfirmationNumberNotEnough{}));
188            }
189            
190            entry.executed.set(true);
191            
192            // Execute the transaction
193            match call(Call::new().value(entry.value.get()), entry.to.get(), &entry.data.get_bytes()) {
194                // If the transaction is successful, emit the `ExecuteTransaction` event.
195                Ok(_) => {
196                    evm::log(ExecuteTransaction {
197                        owner: msg::sender(),
198                        txIndex: U256::from(tx_index),
199                    });
200                    Ok(())
201                },
202                // If the transaction fails, revert the transaction.
203                Err(_) => {
204                    return Err(MultiSigError::ExecuteFailed(ExecuteFailed{}));
205                }
206            }
207            
208        } else {
209            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
210        }
211    }
212
213    // The `confirm_transaction` method confirms a transaction.
214    pub fn confirm_transaction(&mut self, tx_index: U256) -> Result<(), MultiSigError> {
215        // The sender must be an owner.
216        if !self.is_owner.get(msg::sender()) {
217            return Err(MultiSigError::NotOwner(NotOwner{}));
218        }
219
220        // The transaction must exist.
221        if tx_index >= U256::from(self.transactions.len()) {
222            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
223        }
224
225        // Try get transaction and check transaction is valid or not, if valid, confirm it, if not, revert tx.
226        if let Some(mut entry) = self.transactions.get_mut(tx_index) {
227            if entry.executed.get() {
228                return Err(MultiSigError::TxAlreadyExecuted(TxAlreadyExecuted{}));
229            }
230
231            if self.is_confirmed.get(tx_index).get(msg::sender()) {
232                return Err(MultiSigError::TxAlreadyConfirmed(TxAlreadyConfirmed{}));
233            }
234
235            // Confirm the transaction
236            let num_confirmations = entry.num_confirmations.get();
237            entry.num_confirmations.set(num_confirmations + U256::from(1));
238            // Set the transaction as confirmed by the sender.
239            let mut tx_confirmed_info = self.is_confirmed.setter(tx_index);
240            let mut confirmed_by_address = tx_confirmed_info.setter(msg::sender());
241            confirmed_by_address.set(true);
242
243            // Emit the `ConfirmTransaction` event.
244            evm::log(ConfirmTransaction {
245                owner: msg::sender(),
246                txIndex: U256::from(tx_index),
247            });
248            Ok(())
249        } else {
250            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
251        }
252    }
253
254    // The `revoke_confirmation` method revokes a confirmation for a transaction.
255    pub fn revoke_confirmation(&mut self, tx_index: U256) -> Result<(), MultiSigError> {
256        // The sender must be an owner.
257        if !self.is_owner.get(msg::sender()) {
258            return Err(MultiSigError::NotOwner(NotOwner{}));
259        }
260        // let tx_index = tx_index.to;
261        if tx_index >= U256::from(self.transactions.len()) {
262            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
263        }
264
265        // Try get transaction and check transaction is valid or not, if valid, revoke it, if not, revert tx.
266        if let Some(mut entry) = self.transactions.get_mut(tx_index) {
267            // Check if the transaction has been confirmed or not
268            if !self.is_confirmed.get(tx_index).get(msg::sender()) {
269                // If the transaction has not been confirmed, return an error.
270                return Err(MultiSigError::TxNotConfirmed(TxNotConfirmed{}));
271            }
272
273            //  Revoke the transaction
274            let num_confirmations = entry.num_confirmations.get();
275            entry.num_confirmations.set(num_confirmations - U256::from(1));
276            // Set the transaction as not confirmed by the sender.
277            let mut tx_confirmed_info = self.is_confirmed.setter(tx_index);
278            let mut confirmed_by_address = tx_confirmed_info.setter(msg::sender());
279            confirmed_by_address.set(false);
280
281            //  Emit the `RevokeConfirmation` event.
282            evm::log(RevokeConfirmation {
283                owner: msg::sender(),
284                txIndex: U256::from(tx_index),
285            });
286            Ok(())
287        } else {
288            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
289        }
290    }
291
292    // The `is_owner` method checks if an address is an owner.
293    pub fn is_owner(&self, check_address: Address) -> bool {
294        self.is_owner.get(check_address)
295    }
296
297    // The `get_transaction_count` method returns the number of transactions.
298    pub fn get_transaction_count(&self) -> U256 {
299        U256::from(self.transactions.len())
300    }
301}
1// Allow `cargo stylus export-abi` to generate a main function.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5/// Use an efficient WASM allocator.
6#[global_allocator]
7static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
8
9/// Import items from the SDK. The prelude contains common traits and macros.
10use stylus_sdk::{contract, evm, msg, prelude::*, call::{Call, call}, alloy_primitives::{Address, U256}, abi::Bytes};
11use alloy_sol_types::sol;
12
13// Define some events using the Solidity ABI.
14sol! {
15    event Deposit(address indexed sender, uint256 amount, uint256 balance);
16    event SubmitTransaction(
17        address indexed owner,
18        uint256 indexed txIndex,
19        address indexed to,
20        uint256 value,
21        bytes data
22    );
23    event ConfirmTransaction(address indexed owner, uint256 indexed txIndex);
24    event RevokeConfirmation(address indexed owner, uint256 indexed txIndex);
25    event ExecuteTransaction(address indexed owner, uint256 indexed txIndex);
26
27    // Error types for the MultiSig contract
28    error AlreadyInitialized();
29    error ZeroOwners(); // The owners number is 0 when init.
30    error InvaildConfirmationNumber();
31    error InvalidOwner(); // The owner address is invalid when init.
32    error OwnerNotUnique(); // The owner address is not unique when init.
33    error NotOwner(); // The sender is not an owner.
34    error TxDoesNotExist();
35    error TxAlreadyExecuted();
36    error TxAlreadyConfirmed();
37    error TxNotConfirmed();
38    error ConfirmationNumberNotEnough();
39    error ExecuteFailed();
40}
41
42// Define some persistent storage using the Solidity ABI.
43// `MultiSig` will be the entrypoint.
44sol_storage! {
45    #[entrypoint]
46    pub struct MultiSig {
47        address[] owners; // The addresses of the owners
48        mapping(address => bool) is_owner; // mapping from owner => bool
49        uint256 num_confirmations_required; // The number of confirmations required to execute a transaction
50        TxStruct[] transactions; // The transactions array
51        // mapping from tx index => owner => bool
52        mapping(uint256 => mapping(address => bool)) is_confirmed;
53    }
54
55    // Define the `TxStruct` struct
56    pub struct TxStruct {
57        address to;
58        uint256 value;
59        bytes data;
60        bool executed; // Whether the transaction has been executed
61        uint256 num_confirmations; // The number of confirmations of the current transaction
62    }
63}
64
65// Error types for the MultiSig contract
66#[derive(SolidityError)]
67pub enum MultiSigError {
68    AlreadyInitialized(AlreadyInitialized),
69    ZeroOwners(ZeroOwners),
70    InvaildConfirmationNumber(InvaildConfirmationNumber),
71    InvalidOwner(InvalidOwner),
72    OwnerNotUnique(OwnerNotUnique),
73    NotOwner(NotOwner),
74    TxDoesNotExist(TxDoesNotExist),
75    TxAlreadyExecuted(TxAlreadyExecuted),
76    TxAlreadyConfirmed(TxAlreadyConfirmed),
77    TxNotConfirmed(TxNotConfirmed),
78    ConfirmationNumberNotEnough(ConfirmationNumberNotEnough),
79    ExecuteFailed(ExecuteFailed),
80}
81
82/// Declare that `MultiSig` is a contract with the following external methods.
83#[external]
84impl MultiSig {
85    pub fn num_confirmations_required(&self) -> Result<U256, MultiSigError> {
86        Ok(self.num_confirmations_required.get())
87    }
88
89    // The `deposit` method is payable, so it can receive funds.
90    #[payable]
91    pub fn deposit(&mut self) {
92        let sender = msg::sender();
93        let amount = msg::value();
94        evm::log(
95            Deposit{
96                sender: sender, 
97                amount: amount, 
98                balance: contract::balance()
99            });
100    }
101
102    // The `submit_transaction` method submits a new transaction to the contract.
103    pub fn submit_transaction(&mut self, to: Address, value: U256, data: Bytes) -> Result<(), MultiSigError> {
104        // The sender must be an owner.
105        if !self.is_owner.get(msg::sender()) {
106            return Err(MultiSigError::NotOwner(NotOwner{}));
107        }
108
109        let tx_index = U256::from(self.transactions.len());
110        
111        // Add the transaction to the transactions array.
112        let mut new_tx = self.transactions.grow();
113        new_tx.to.set(to);
114        new_tx.value.set(value);
115        new_tx.data.set_bytes(data.clone());
116        new_tx.executed.set(false);
117        new_tx.num_confirmations.set(U256::from(0));
118
119        // Emit the `SubmitTransaction` event.
120        evm::log(SubmitTransaction {
121            owner: msg::sender(),
122            txIndex: tx_index,
123            to: to,
124            value: value,
125            data: data.to_vec(),
126        });
127        Ok(())
128    }
129
130
131    // The `initialize` method initializes the contract with the owners and the number of confirmations required.
132    pub fn initialize(&mut self, owners: Vec<Address>, num_confirmations_required: U256) -> Result<(), MultiSigError> {
133        // The owners must not be initialized.
134        if self.owners.len() > 0 {
135            return Err(MultiSigError::AlreadyInitialized(AlreadyInitialized{}));
136        }
137
138        // The owners must not be empty.
139        if owners.len() == 0 {
140            return Err(MultiSigError::ZeroOwners(ZeroOwners{}));
141        }
142
143        // The number of confirmations required must be greater than 0 and less than or equal to the number of owners.
144        if num_confirmations_required == U256::from(0) || num_confirmations_required > U256::from(owners.len()) {
145            return Err(MultiSigError::InvaildConfirmationNumber(InvaildConfirmationNumber{}));
146        }
147
148        // Add the owners to the contract.
149        for owner in owners.iter() {
150            if *owner == Address::default() {
151                return Err(MultiSigError::InvalidOwner(InvalidOwner{}))
152            }
153
154            if self.is_owner.get(*owner) {
155                return Err(MultiSigError::OwnerNotUnique(OwnerNotUnique{}))
156            }
157
158            self.is_owner.setter(*owner).set(true);
159            self.owners.push(*owner);
160        }
161
162        // Set the number of confirmations required.
163        self.num_confirmations_required.set(num_confirmations_required);
164        Ok(())
165    }
166
167    // The `execute_transaction` method executes a transaction.
168    pub fn execute_transaction(&mut self, tx_index: U256) -> Result<(), MultiSigError>{
169        // The sender must be an owner.
170        if !self.is_owner.get(msg::sender()) {
171            return Err(MultiSigError::NotOwner(NotOwner{}));
172        }
173
174        // The transaction must exist.
175        let tx_index = tx_index.to::<usize>();
176        if tx_index >= self.transactions.len() {
177            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
178        }
179
180        // Try get transaction and check transaction is valid or not, if valid, execute it, if not, revert tx.
181        if let Some(mut entry) = self.transactions.get_mut(tx_index) {
182            if entry.executed.get() {
183                return Err(MultiSigError::TxAlreadyExecuted(TxAlreadyExecuted{}));
184            }
185
186            if entry.num_confirmations.get() < self.num_confirmations_required.get() {
187                return Err(MultiSigError::ConfirmationNumberNotEnough(ConfirmationNumberNotEnough{}));
188            }
189            
190            entry.executed.set(true);
191            
192            // Execute the transaction
193            match call(Call::new().value(entry.value.get()), entry.to.get(), &entry.data.get_bytes()) {
194                // If the transaction is successful, emit the `ExecuteTransaction` event.
195                Ok(_) => {
196                    evm::log(ExecuteTransaction {
197                        owner: msg::sender(),
198                        txIndex: U256::from(tx_index),
199                    });
200                    Ok(())
201                },
202                // If the transaction fails, revert the transaction.
203                Err(_) => {
204                    return Err(MultiSigError::ExecuteFailed(ExecuteFailed{}));
205                }
206            }
207            
208        } else {
209            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
210        }
211    }
212
213    // The `confirm_transaction` method confirms a transaction.
214    pub fn confirm_transaction(&mut self, tx_index: U256) -> Result<(), MultiSigError> {
215        // The sender must be an owner.
216        if !self.is_owner.get(msg::sender()) {
217            return Err(MultiSigError::NotOwner(NotOwner{}));
218        }
219
220        // The transaction must exist.
221        if tx_index >= U256::from(self.transactions.len()) {
222            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
223        }
224
225        // Try get transaction and check transaction is valid or not, if valid, confirm it, if not, revert tx.
226        if let Some(mut entry) = self.transactions.get_mut(tx_index) {
227            if entry.executed.get() {
228                return Err(MultiSigError::TxAlreadyExecuted(TxAlreadyExecuted{}));
229            }
230
231            if self.is_confirmed.get(tx_index).get(msg::sender()) {
232                return Err(MultiSigError::TxAlreadyConfirmed(TxAlreadyConfirmed{}));
233            }
234
235            // Confirm the transaction
236            let num_confirmations = entry.num_confirmations.get();
237            entry.num_confirmations.set(num_confirmations + U256::from(1));
238            // Set the transaction as confirmed by the sender.
239            let mut tx_confirmed_info = self.is_confirmed.setter(tx_index);
240            let mut confirmed_by_address = tx_confirmed_info.setter(msg::sender());
241            confirmed_by_address.set(true);
242
243            // Emit the `ConfirmTransaction` event.
244            evm::log(ConfirmTransaction {
245                owner: msg::sender(),
246                txIndex: U256::from(tx_index),
247            });
248            Ok(())
249        } else {
250            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
251        }
252    }
253
254    // The `revoke_confirmation` method revokes a confirmation for a transaction.
255    pub fn revoke_confirmation(&mut self, tx_index: U256) -> Result<(), MultiSigError> {
256        // The sender must be an owner.
257        if !self.is_owner.get(msg::sender()) {
258            return Err(MultiSigError::NotOwner(NotOwner{}));
259        }
260        // let tx_index = tx_index.to;
261        if tx_index >= U256::from(self.transactions.len()) {
262            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
263        }
264
265        // Try get transaction and check transaction is valid or not, if valid, revoke it, if not, revert tx.
266        if let Some(mut entry) = self.transactions.get_mut(tx_index) {
267            // Check if the transaction has been confirmed or not
268            if !self.is_confirmed.get(tx_index).get(msg::sender()) {
269                // If the transaction has not been confirmed, return an error.
270                return Err(MultiSigError::TxNotConfirmed(TxNotConfirmed{}));
271            }
272
273            //  Revoke the transaction
274            let num_confirmations = entry.num_confirmations.get();
275            entry.num_confirmations.set(num_confirmations - U256::from(1));
276            // Set the transaction as not confirmed by the sender.
277            let mut tx_confirmed_info = self.is_confirmed.setter(tx_index);
278            let mut confirmed_by_address = tx_confirmed_info.setter(msg::sender());
279            confirmed_by_address.set(false);
280
281            //  Emit the `RevokeConfirmation` event.
282            evm::log(RevokeConfirmation {
283                owner: msg::sender(),
284                txIndex: U256::from(tx_index),
285            });
286            Ok(())
287        } else {
288            return Err(MultiSigError::TxDoesNotExist(TxDoesNotExist{}));
289        }
290    }
291
292    // The `is_owner` method checks if an address is an owner.
293    pub fn is_owner(&self, check_address: Address) -> bool {
294        self.is_owner.get(check_address)
295    }
296
297    // The `get_transaction_count` method returns the number of transactions.
298    pub fn get_transaction_count(&self) -> U256 {
299        U256::from(self.transactions.len())
300    }
301}

Cargo.toml

1[package]
2name = "stylus-multisig-example"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "0.3.1"
8alloy-sol-types = "0.3.1"
9mini-alloc = "0.4.2"
10stylus-sdk = { version = "0.5.0", features = ["docs"] }
11hex = "0.4.3"
12
13[features]
14export-abi = ["stylus-sdk/export-abi"]
15debug = ["stylus-sdk/debug"]
16
17[lib]
18crate-type = ["lib", "cdylib"]
19
20[profile.release]
21codegen-units = 1
22strip = true
23lto = true
24panic = "abort"
25opt-level = "s"
1[package]
2name = "stylus-multisig-example"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "0.3.1"
8alloy-sol-types = "0.3.1"
9mini-alloc = "0.4.2"
10stylus-sdk = { version = "0.5.0", features = ["docs"] }
11hex = "0.4.3"
12
13[features]
14export-abi = ["stylus-sdk/export-abi"]
15debug = ["stylus-sdk/debug"]
16
17[lib]
18crate-type = ["lib", "cdylib"]
19
20[profile.release]
21codegen-units = 1
22strip = true
23lto = true
24panic = "abort"
25opt-level = "s"