The term "multisig" stands for "multi-signature" and it's used to describe accounts associated with more than one private key. The account is created with a list of owners and a policy
m-out-of-n
; when transactions are approved by at least m owners, they can be executed.In blockchain each transaction involves the usage of public-key signature; each blockchain participant owns a private/public key pair. The participants use the private keys to sign a transaction and all other participants can verify that signature using the signer's public key.
With a multisig account, each transaction requires the signature of many accounts before being executed. One participant (who is among the multisig account owners) can start the transaction, but only when the other owners approve that transaction, it will be executed.
The multisig account eliminates the single point of failure in case of attacks and it allows the implementation of a variety of use cases where one or many parties are involved. Here below some examples.
Redundancy: if a user loses the key, the funds are not lost because the other keys can still be used.
2FA (two-factor authentication): a single user can have both a web wallet and a mobile wallet associated with a multisig account, policy 2-of-2.
Departments approval: two departments must approve each transaction before being published, policy 2-of-2.
Parents' saving account: a kid can spend money only with the approval of either parent.
On the repository you can find an example with some of the features listed above, you can clone it and run it locally.
git clone [email protected]:rsksmart/rif-multisig-node-sample.git
npm install
npm run network:local
npm run start
to execute the main scriptLibraries:
Smart contracts:
Rest API:
trace_transaction
and trace_blocks
. Furthermore, it allows collecting off-chain signatures and retrieving multisig pending transactions.Repository:
Create a multisig account
Initialize the signer:
const provider = new providers.JsonRpcProvider()
const signer = await provider.getSigner(0)
Before creating a multisig account, it requires that the contracts have been deployed to the network. Using one of the RSK networks (mainnet or testnet), you can use the addresses of the pre-deployed contracts.
import { EthersSafeFactory } from '@rsksmart/safe-factory-sdk'
const safeFactory = new EthersSafeFactory(
signer,
proxyFactoryAddress,
safeSingletonAddress
)
const safeSdk = await safeFactory.createSafe({
owners: [signer.getAddress()],
threshold: 1
})
Transfer funds to the multisig account
It is important to understand that, to receive funds, we have to use the multisig account address (instead of the owner's address) as a receiver.
// previously created safe
const safeSdk: Safe;
// address to use as fund receiver
const multisigAccountAddress = safeSdk.getAddress()
Create multisig transactions
There are several ways to create multisig transactions according to our needs.
If we need to create raw transactions we could use the createTransaction method on the Safe instance:
const partialTx: SafeTransactionDataPartial = {
to: '0x<address>',
data: '0x<data>',
value: '<eth_value_in_wei>'
}
const safeTransaction = await safeSdk.createTransaction(partialTx)
or we could use the
RawTransactionBuilder
:import { RawTransactionBuilder } from '@rsksmart/safe-transactions-sdk'
const rawTransactionBuilder = new RawTransactionBuilder(safe)
const safeTransaction = await rawTransactionBuilder.rawTransaction(to, value, data)
ERC20 transactions
Similarly, we can create ERC20-related transactions, including RIF Token.
import { ERC20TransactionBuilder } from '@rsksmart/safe-transactions-sdk'
const erc20TransactionBuilder = ERC20TransactionBuilder.create(safe, ERC20Token)
// create a `transfer` transaction
const transferTx = await erc20TransactionBuilder.transfer(
to,
transfer
)
// create a `transferFrom` transaction
const transferFromTx = await erc20TransactionBuilder.transferFrom(
from,
to,
value
)
ERC721 transactions
The package @rsksmart/safe-transactions-sdk provides also an easy way to create ERC721 transactions.
import { ERC721TransactionBuilder } from "@rsksmart/safe-transactions-sdk";
const erc721TransactionBuilder = ERC721TransactionBuilder.create(
safe,
ERC721Token
);
// create a `transferFrom` transaction
const transferFromTx = await erc721TransactionBuilder.transferFrom(
from,
to,
tokenId
);
// create a `safeTransferFrom` transaction
const safeTransferFromTx = await erc721TransactionBuilder.safeTransferFrom(
from,
to,
tokenId
);
Approve multisig transactions
Transaction approval can take place both on-chain and off-chain. With the on-chain approval, the signature is added interacting with the safe account, hence the signature is available in the blockchain. Off-chain approval requires the user to publish the signature through the Safe Transaction Service to make it available to other participants.
On-chain approval
const txHash = await safeSdk.getTransactionHash(safeTransaction)
const approveTxResponse = await safeSdk.approveTransactionHash(txHash)
await approveTxResponse.wait()
Once the transaction is approved on-chain, user can retrieve the list of owners with the
getOwnersWhoApprovedTx
method.const ownersWhoApproved = await safeSdk.getOwnersWhoApprovedTx(txHash)
Off-chain approval
The user needs to create the signature first.
const signature = await safeSdk.signTransaction(safeTransaction)
Once the signature is generated, the user must publish the signature using the Safe Transaction Service.
const safeService = new SafeServiceClient(SAFE_TRANSACTION_SERVICE_URL)
const safeTxHash = await safeCoreSdk.getTransactionHash(transaction)
await safeServiceClient.confirmTransaction(safeTxHash, signature.data)
Execute multisig transactions
Once a transaction is approved by a number of owners that must be equal or greater than the threshold set, it can be executed.
const txResponse = await safeSDk.executeTransaction(safeTransaction)
await txResponse.wait()
Reject multisig transactions
Transaction rejection implies the creation and execution of a new transaction with the same nonce like the one we want to reject. Once the rejection transaction is created, it must be approved and executed like any other multisig transaction.
For instance, if the current threshold is set to 2, rejecting a transaction requires the following steps:
import { rejectTx } from '@rsksmart/safe-transactions-sdk'
// creation of the rejection transaction
const rejectionTx = await rejectTx(safe, transaction)
Update multisig account (owners and threshold)
As per any other transaction, changing the main properties of a multisig account requires the creation of a new transaction and its approval, before being executed.
// Create a transaction to add a new owner
const ownerTx = await safe.getAddOwnerTx(newOwner, newThreshold)
// Create a transaction to remove an owner
const ownerTx = await safe.getRemoveOwnerTx(existingOwner, newThreshold)
// Create a transaction to replace an owner
const ownerTx = await safe.getSwapOwnerTx(oldOwner, newOwner)
It is important to keep in mind that
newThreshold
must be set according to the current owners. If the current owners are three, and we add a new owner, the newThreshold
cannot be set to four, but it can be at most three, as the current number of owners.const thresholdTx = await safe.getChangeThresholdTx(newThreshold)
Eventually, it’s also possible to change the
threshold
without changing the owners.The operations listed above won’t change the safe account, they create the related transactions. The changes will be applied only after the transaction is executed.
Please contact us through our open Slack for technical questions and support.
You can also reach out with any feedback you would like to share with us through our social media channels and forums: