MultiSafe is a shared crypto wallet for managing Stacks (STX) and Bitcoin (BTC).
Current supported features:
- Send, transfer and hold STX
- Supports up to 20 owners
Future updates (coming soon):
- User-friendly app
- SIP-009 Non-fungible token and SIP-010 Fungible token support
- Native Bitcoin (BTC) support
- Please be aware that the code hasn't been audited by an independent security team. Use at your own risk.
- We are planning a security audit for June 2022
- We would love your help testing our code. Please report any bugs as a Github issue.
Install Clarinet: https://github.com/hirosystems/clarinet
MultiSafe is written in Clarity and therefore requires you to install Clarinet locally on the command line. Type the following command to check that you have Clarinet installed:
$ clarinet --version
Please make sure you have Clarinet version 0.31.1 or higher installed.
- Clone the repo:
$ git clone https://github.com/Trust-Machines/multisafe.git && cd multisafe
- Run the unit test to confirm all the tests passed and everything is working:
$ clarinet test
- Open the Clarinet console in order to interact with the contract
$ clarinet console
Go to /contracts/safe.clar
. At the bottom of that contract you'll see a list with three sample owners. These are the default Clarinet wallets that you can use for testing locally in Clarinet.
(init (list
'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5
'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG
'ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC
) u2)
If you'd like to deploy to mainnet/testnet update those wallet addresses with your own wallet addresses.
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe get-owners)
In the example above ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe
is your safe's contract name.
get-owners
will return [ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5, ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG, ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC]
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe get-threshold)
The Clarinet console demo will return u2
In this example “u2” tells us that we need a minimum of 2 confirmations in order to approve a transaction
- Change the transaction sender to “wallet_1”
::set_tx_sender ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5
- Add a new user to the wallet. In this example, we add “wallet_9” to MultiSafe.
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe submit 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.add-owner 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ft-none 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-none (some 'STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6) none none)
ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM = deployer 'STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 = wallet_9
- If you “get-owners” you’ll see that there are still only 3 owners. In order to confirm the 4th owner you will need to switch to “wallet_2” or “wallet_3” (the other owner”) and approve the transaction.
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe get-owners)
Returns: [ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5, ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG, ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC]
- Change owner to wallet_2
::set_tx_sender ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG
- As wallet_2 owner, now you can approve the 4th new owner
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe confirm u0 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.add-owner 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ft-none 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-none)
- Call “get-owners” again and you will see the 4th owner has been added
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe get-owners)
Returns: [ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5, ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG, ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC, STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6]
If you are the wallet_3 owner (ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC) then the following command will allow you to send STX from your wallet to the MultiSafe
(stx-transfer? u5000 'ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe)
::get_assets_maps
will show you that the STX has been transfered into the .safe contract
In this example, using Clarinet console, we will send STX from MultiSafe to "wallet_7"
- Send STX to wallet_7
Assuming you are an owner — in this example, you will send 1000 uSTX from the MultisSafe to wallet_7 (ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ).
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe submit 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.transfer-stx 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe
'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ft-none 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-none (some 'ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ) (some u1000) none)
- Change owner to wallet_2
::set_tx_sender ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG
- Wallet_2 can now confirm the transaction
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe confirm u1 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.transfer-stx 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.safe 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ft-none 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-none)
MultiSafe now has -1000 uSTX, and wallet_7 +1000 uSTX
The following function can be run by only safe owners:
submit (executor <executor-trait>, safe <safe-trait>, param-ft <ft-trait>, param-nft <nft-trait>, param-p (optional principal), param-u (optional uint), param-b (optional (buff 20))) => (response uint)
confirm (tx-id uint, executor <executor-trait>, safe <safe-trait>, param-ft <ft-trait>, param-nft <nft-trait>) => (response bool)
revoke (tx-id uint) => (response bool)
The following functions can be run by only safe contract itself and requires the minimum number of confirmations:
add-owner (owner principal) => (response bool)
remove-owner (owner principal) => (response bool)
set-threshold (value uint) => (response bool)
get-version() => string-ascii
get-owners() => list
get-threshold() => uint
get-nonce() => uint
get-info() => tuple
get-transaction (tx-id uint) => tuple
get-transactions (tx-ids (list 20 uint)) => list