深入gnosis-safe
Build a treasury wallet with multisignature Gnosis Safe - LogRocket Blog
Imagine you and your friends are building an NFT marketplace. You are the CEO and your friend works as a Solidity engineer who writes the smart contract. The NFT marketplace becomes popular, and your revenue builds from the market fee of every NFT sale transaction. You store your profit inside a smart contract, and boast to the media about your company that has enough money to buy a private island. Then, the Solidity engineer disappears and withdraws all the funds from the treasury. You watch in horror.
Now, you vow not to fall into the same trap again. From now on, every sensitive transaction in a smart contract needs approval from a certain number of people. For example, withdrawing funds from your treasury requires at least 60 percent approval from certain key people. If there are five key people, at least three approvals are needed.
Luckily, you don’t need to build this mechanism from scratch; you can use Gnosis Safe to interact with your NFT marketplace. You put the funds inside the Gnosis Safe smart contracts, and withdrawing the funds now requires at least a certain number of signatures. A rogue agent cannot steal the funds anymore, and you’re back to saving up for a private island!
Gnosis Safe is a project from Gnosis. Gnosis started as a prediction markets platform where people can trade information freely. As part of the project, the team behind Gnosis created Gnosis Safe to secure funds for multiple participants. Today, it’s the most popular multisig wallet smart contract on Ethereum. Search “multisig wallet Ethereum” on Google and you will find Gnosis in the top results.
In this article, you will learn how to set up a treasury wallet with Gnosis Safe, so you can protect your funds on the Ethereum blockchain.
Setting up Gnosis Safe smart contracts
You can clone the Gnosis Safe smart contract from their GitHub repo like so:
1 | git clone https://github.com/gnosis/safe-contracts.git |
Use a specific version so you can follow this tutorial:
1 | cd safe-contracts |
With this specific version, the deployed addresses of Gnosis Safe will be deterministic.
Let’s deploy Gnosis Safe to the Hardhat development network. But first, you must install Hardhat inside the safe-contracts
directory:
1 | yarn add hardhat |
Then, run the Hardhat development network and deploy the Gnosis Safe smart contracts:
1 | npx hardhat node |
The Gnosis Safe smart contracts are not just one smart contract; they are many. But you need to pay attention to three smart contracts in particular: MultiSend
, GnosisSafe
, and GnosisSafeProxyFactory
. You need their addresses when you use the Gnosis Safe SDK later.
So what are these three smart contracts?
GnosisSafe
is the core safe smart contract, and everyone only needs one. Your startup and your business competitors can use the same deployed GnosisSafe
, so you don’t need to deploy it separately if it has been deployed already.
You don’t interact directly with GnosisSafe
. You use a proxy, called GnosisSafeProxy
. Because of this, your startup and your business competitor need to use different GnosisSafeProxy
s. To create your own GnosisSafeProxy
, you can use GnosisSafeProxyFactory
.
MultiSend
is a helper smart contract to batch multiple transactions into one. You may want to buy a yacht as your startup office, pay salary to a meme artist, and pay taxes to the country where your startup resides. Instead of executing these transactions one by one, you can batch them into one with this helper smart contract, then execute them in one go.
There are other smart contracts as part of the Gnosis Safe, but you don’t touch them directly. You only deal with the three smart contracts mentioned previously. That’s why the Gnosis Safe SDK requires you to provide their addresses.
If you use Gnosis Safe in other networks like Ethereum mainnet or Rinkeby, you don’t need to deploy the Gnosis Safe smart contracts because the team behind Gnosis Safe already deployed these core ones. You just need to find their addresses and write them down. But since you are using the Hardhat development network, you need to do this step.
Let the process run peacefully. You can open a new terminal and create your project that interacts with these smart contracts in the new terminal.
Installing the Gnosis Safe SDK libraries
The Gnosis Safe SDK libraries are Node.js libraries. To use them, you need to create a Node project. Let’s create one by creating an empty directory and initialize it with yarn:
1 | mkdir our-treasury |
To interact with Ethereum in Node, you have two choices: web3.js
and ethers.js
. In this tutorial, you’ll use the ethers.js
library. Install the library with yarn like so:
1 | yarn add ethers |
You will put the Ethereum addresses on the .env
file instead of hard-coding them, so you need the dotenv
library:
1 | yarn add dotenv |
Lastly, you need the Gnosis Safe SDK libraries:
1 | yarn add @gnosis.pm/safe-core-sdk @gnosis.pm/safe-ethers-lib |
These are the core libraries that you’re going to learn how to use in this tutorial to interface with the Gnosis Safe smart contracts.
Setting up the .env file
Instead of hard-coding the Ethereum addresses, you can store them as environment variables. But setting up environment variables in a terminal before executing a script is a hassle:
1 | export ACCOUNT_1=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 |
It’s better if you use the .env
file. Basically, you use the dotenv
library to load the environment variables from the .env
file. That way, you only need to set up the environment variables once.
Create the .env
file with the following content:
1 | ACCOUNT_1="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" |
In the code above, you can see that the addresses for MULTI_SEND_ADDRESS
, SAFE_MASTER_COPY_ADDRESS
, SAFE_PROXY_FACTORY_ADDRESS
are the same as the ones in the first terminal. As a reminder, the first terminal is the terminal where you’ve deployed the Gnosis Safe smart contracts on the Hardhat development network. Later, in your client code, you will load these smart contracts’ addresses from the .env
file to interact with the deployed smart contracts on the Hardhat development network.
The ACCOUNT_1
and other accounts are the sample Ethereum addresses provided by the Hardhat development node. If you run npx hardhat node
in a new Hardhat project, you will get 20 sample Ethereum addresses with their private keys for development. Each account has 10,000ETH. In this .env
file, you just took the first seven addresses.
Creating the treasury
Let’s create the index.js
file. This is where you will write the code to build the treasury with Gnosis Safe:
1 | edit index.js # replace edit with vim or code or your favorite editor |
First things first, you want to be able to read the variables you put in the .env
file. So add this line:
1 | require('dotenv').config(); |
Then, you import the Gnosis Safe SDK library:
1 | const { SafeFactory, SafeAccountConfig, ContractNetworksConfig } = require('@gnosis.pm/safe-core-sdk'); |
To make things clearer, imagine that you’re creating a web3 startup to disrupt traditional banks. There are five people who are building this startup, with you acting as the CEO. The other people on the team are the CTO, a Solidity engineer, a meme artist, and an advisor.
An angel investor sends money to your startup. The money is put inside the safe smart contract. It takes three of five signatures from your team to approve any transactions related to this smart contract. You, as the CEO, decide to buy a yacht for your startup office. You will need two other signatures from your team to approve this transaction. Let’s do it!
Setting up multisignature authorization in Gnosis Safe wallet
To protect your company’s treasury from being emptied by a single member, you have to make sure that three out of your five team members approve of the yacht purchase. Let’s add this functionality now.
Still in the same file, index.js
, add these lines below the const Safe = require…
line:
1 | const ceo = process.env.ACCOUNT_1; |
Here, you load up the addresses you stored on the .env
file in index.js
. This way, to change the addresses, you don’t need to change the code.
Then, set up a provider from the ethers.js
library:
1 | const { ethers } = require('ethers'); |
A provider is an Ethereum connection object. Here, you create an abstraction of a JSON-RPC connection to an Ethereum node.
From this provider, you create three signers from three addresses. Remember, you only need three signatures to approve a transaction in the safe:
1 | const ceo_signer = provider.getSigner(ceo); |
The Gnosis Safe smart contracts work with the ethers.js
library and the web3.js
library. In this tutorial, you are using ethers.js
. So you need the adapter that works with ethers.js
:
1 | const EthersAdapter = require('@gnosis.pm/safe-ethers-lib')["default"]; |
In the code above, you interacted with the Gnosis Safe SDK using these adapters. Note that each address needs its own adapter.
Next, create the main
, asynchronous function. You will use this pattern to handle async/await
and handle errors in the code properly:
1 | async function main() { |
As explained in the beginning of the tutorial, the only way to create a safe is from the safe factory that is shared with everyone. So first, you need to create a safe factory object connecting to the safe factory smart contract, GnosisSafeProxyFactory
:
1 | const id = await ethAdapter_ceo.getChainId(); |
In the code above, you first received the chain ID. Then, you created an object containing three smart contracts with which you safe will interact. Finally, you created a safe factory.
Next, create a safe from this safe factory like so:
1 | const owners = [ceo, cto, meme_artist, solidity_engineer, advisor]; |
The safe needs the addresses of the members and the minimum amount of signatures required to approve transactions for this safe. In the code above, you put all members of the startup and 3
as the threshold. To deploy a safe, you can use the deploySafe
method from the safe factory.
Now that you have a safe already, an investor sends money to your startup, which would look like this:
1 | const treasury = safeSdk_ceo.getAddress(); |
The safe holds your treasury. The investor sent 10ETH using the eth_sendTransaction
RPC method.
To make sure it works, you can check the balance of the treasury with the following:
1 | const balance = await safeSdk_ceo.getBalance(); |
Obtaining multisignature approval
Once the investor’s money is in, you can move fast. Create a transaction to buy a yacht like so:
1 | const three_ethers = ethers.utils.parseUnits("3", 'ether').toHexString(); |
The transaction is sending 3ETH to the yacht shop. Since the transaction is transferring ETH, you can fill empty data, 0x
, in the data field of the transaction. However, if you create a smart contract transaction, such as minting NFTs or selling tokens, you will need to fill the data field.
After doing that, create the safe transaction using the createTransaction
method. Then, get the hash with the getTransactionHash
method.
Finally, your job as CEO is to approve the transaction:
1 | const txResponse = await safeSdk_ceo.approveTransactionHash(hash); |
But your job is not done yet. You call your co-founder, the CTO of your startup, and persuade her to approve the transaction of buying a yacht. “Wouldn’t it be nice if you could code in the vast ocean?”
Your CTO agrees to approve the transaction:
1 | const safeSdk_cto = await Safe.create({ ethAdapter: ethAdapter_cto, |
The CTO needs a different Safe object. But you don’t need to create it with the safe factory; you can create it with the create
method of the Safe object. Because your safe smart contract is live already on the blockchain, you just passed the treasury address when you created the Safe object.
Next, you pass the transaction to your CTO either by chatting or via email. What the transaction means in this context is the transaction
object in the code. Remember, you’ve already created this object:
1 | const transaction = { |
Your CTO created a safe transaction from this one and got its hash. Then, she approves the transaction using the approveTransactionHash
method, which accepts the hash argument.
Your job is still not done yet; you need another signature. But this time, you don’t need to convince your advisor because he gives you full support to buy a yacht. He approves the transaction:
1 | const safeSdk_advisor = await Safe.create({ ethAdapter: ethAdapter_advisor, |
The code is the same as the CTO’s approval transaction, but instead of the approveTransactionHash
method, the advisor used the executeTransaction
method. This method approves the transaction as well behind the scenes. But most importantly, this method executes the safe transaction, which is buying a yacht!
Finally, let’s check your treasury balance:
1 | const afterBalance = await safeSdk_ceo.getBalance(); |
The script is finished. You can execute the script like so:
1 | node index.js |
Now, you can work in a yacht with your team building a DAO to disrupt banks!
Conclusion
In this article, you learned how to create a Gnosis Safe that can be configured to require multiple signatures to approve transactions. You launched the Gnosis Safe smart contracts in the Hardhat development network, then, using the Gnosis safe SDK, created a safe to hold the treasury. Using multiple addresses, you created and approved the transaction of sending ETH.
This article only explains the SDK of interacting with the Gnosis Safe smart contracts. If you want to learn the ins and outs of the smart contract themselves, you can check their GitHub repository! The SDK also has other methods like signing a transaction off-chain. Check their GitHub repository to learn more. The code for this article is available on this GitHub repository.