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

Cargo.toml

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