The Protocol Kit facilitates the interaction with the Safe contracts.
To add the Protocol Kit to your project, run:
yarn add @safe-global/protocol-kit
Returns an instance of the Safe Factory.
import { SafeFactory } from '@safe-global/protocol-kit'
const safeFactory = await SafeFactory.create({ ethAdapter })
-
The
isL1SafeMasterCopy
flagThere are two versions of the Safe contracts: Safe.sol that does not trigger events in order to save gas and SafeL2.sol that does, which is more appropriate for L2 networks.
By default
Safe.sol
will be only used on Ethereum Mainnet. For the rest of the networks where the Safe contracts are already deployed, theSafeL2.sol
contract will be used unless you add theisL1SafeMasterCopy
flag to force the use of theSafe.sol
contract.const safeFactory = await SafeFactory.create({ ethAdapter, isL1SafeMasterCopy: true })
-
The
contractNetworks
propertyIf the Safe contracts are not deployed to your current network, the
contractNetworks
property will be required to point to the addresses of the Safe contracts previously deployed by you.import { ContractNetworksConfig } from '@safe-global/protocol-kit' const chainId = await ethAdapter.getChainId() const contractNetworks: ContractNetworksConfig = { [chainId]: { safeMasterCopyAddress: '<MASTER_COPY_ADDRESS>', safeProxyFactoryAddress: '<PROXY_FACTORY_ADDRESS>', multiSendAddress: '<MULTI_SEND_ADDRESS>', multiSendCallOnlyAddress: '<MULTI_SEND_CALL_ONLY_ADDRESS>', fallbackHandlerAddress: '<FALLBACK_HANDLER_ADDRESS>', signMessageLibAddress: '<SIGN_MESSAGE_LIB_ADDRESS>', createCallAddress: '<CREATE_CALL_ADDRESS>', simulateTxAccessorAddress: '<SIMULATE_TX_ACCESSOR_ADDRESS>', safeMasterCopyAbi: '<MASTER_COPY_ABI>', // Optional. Only needed with web3.js safeProxyFactoryAbi: '<PROXY_FACTORY_ABI>', // Optional. Only needed with web3.js multiSendAbi: '<MULTI_SEND_ABI>', // Optional. Only needed with web3.js multiSendCallOnlyAbi: '<MULTI_SEND_CALL_ONLY_ABI>', // Optional. Only needed with web3.js fallbackHandlerAbi: '<FALLBACK_HANDLER_ABI>', // Optional. Only needed with web3.js signMessageLibAbi: '<SIGN_MESSAGE_LIB_ABI>', // Optional. Only needed with web3.js createCallAbi: '<CREATE_CALL_ABI>', // Optional. Only needed with web3.js simulateTxAccessorAbi: '<SIMULATE_TX_ACCESSOR_ABI>' // Optional. Only needed with web3.js } } const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks })
-
The
safeVersion
propertyThe
SafeFactory
constructor also accepts thesafeVersion
property to specify the Safe contract version that will be deployed. This string can take the values1.0.0
,1.1.1
,1.2.0
,1.3.0
or1.4.1
. If not specified, theDEFAULT_SAFE_VERSION
value will be used.const safeVersion = 'X.Y.Z' const safeFactory = await SafeFactory.create({ ethAdapter, safeVersion })
Deploys a new Safe and returns an instance of the Protocol Kit connected to the deployed Safe. The address of the Master Copy, Safe contract version and the contract (Safe.sol
or SafeL2.sol
) of the deployed Safe will depend on the initialization of the safeFactory
instance.
const safeAccountConfig: SafeAccountConfig = {
owners,
threshold,
to, // Optional
data, // Optional
fallbackHandler, // Optional
paymentToken, // Optional
payment, // Optional
paymentReceiver // Optional
}
const safeSdk = await safeFactory.deploySafe({ safeAccountConfig })
This method can optionally receive the saltNonce
parameter.
const safeAccountConfig: SafeAccountConfig = {
owners,
threshold,
to, // Optional
data, // Optional
fallbackHandler, // Optional
paymentToken, // Optional
payment, // Optional
paymentReceiver // Optional
}
const saltNonce = '<YOUR_CUSTOM_VALUE>'
const safeSdk = await safeFactory.deploySafe({ safeAccountConfig, saltNonce })
Optionally, some properties can be passed as execution options:
const options: Web3TransactionOptions = {
from, // Optional
gas, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const options: EthersTransactionOptions = {
from, // Optional
gasLimit, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const safeSdk = await safeFactory.deploySafe({ safeAccountConfig, safeDeploymentConfig, options })
It can also take an optional callback which receives the txHash
of the Safe deployment transaction prior to returning a new instance of the Protocol Kit:
const callback = (txHash: string): void => {
console.log({ txHash })
}
const safeSdk = await safeFactory.deploySafe({ safeAccountConfig, callback })
Returns an instance of the Protocol Kit connected to a Safe. The provided Safe must be a safeAddress
or a predictedSafe
.
Initialization of a deployed Safe using the safeAddress
property:
import Safe from '@safe-global/protocol-kit'
const safeSdk = await Safe.create({ ethAdapter, safeAddress })
Initialization of a not deployed Safe using the predictedSafe
property. Because Safes are deployed in a deterministic way, passing a predictedSafe
will allow to initialize the SDK with the Safe configuration and use it to some extent before it is deployed:
import Safe, { PredictedSafeProps } from '@safe-global/protocol-kit'
const predictedSafe: PredictedSafeProps = {
safeAccountConfig,
safeDeploymentConfig
}
const safeSdk = await Safe.create({ ethAdapter, predictedSafe })
-
The
isL1SafeMasterCopy
flagThere are two versions of the Safe contracts: Safe.sol that does not trigger events in order to save gas and SafeL2.sol that does, which is more appropriate for L2 networks.
By default
Safe.sol
will be only used on Ethereum Mainnet. For the rest of the networks where the Safe contracts are already deployed, theSafeL2.sol
contract will be used unless you add theisL1SafeMasterCopy
flag to force the use of theSafe.sol
contract.const safeSdk = await Safe.create({ ethAdapter, safeAddress, isL1SafeMasterCopy: true })
-
The
contractNetworks
propertyIf the Safe contracts are not deployed to your current network, the
contractNetworks
property will be required to point to the addresses of the Safe contracts previously deployed by you.import { ContractNetworksConfig } from '@safe-global/protocol-kit' const chainId = await ethAdapter.getChainId() const contractNetworks: ContractNetworksConfig = { [chainId]: { safeMasterCopyAddress: '<MASTER_COPY_ADDRESS>', safeProxyFactoryAddress: '<PROXY_FACTORY_ADDRESS>', multiSendAddress: '<MULTI_SEND_ADDRESS>', multiSendCallOnlyAddress: '<MULTI_SEND_CALL_ONLY_ADDRESS>', fallbackHandlerAddress: '<FALLBACK_HANDLER_ADDRESS>', signMessageLibAddress: '<SIGN_MESSAGE_LIB_ADDRESS>', createCallAddress: '<CREATE_CALL_ADDRESS>', simulateTxAccessorAddress: '<SIMULATE_TX_ACCESSOR_ADDRESS>', safeMasterCopyAbi: '<MASTER_COPY_ABI>', // Optional. Only needed with web3.js safeProxyFactoryAbi: '<PROXY_FACTORY_ABI>', // Optional. Only needed with web3.js multiSendAbi: '<MULTI_SEND_ABI>', // Optional. Only needed with web3.js multiSendCallOnlyAbi: '<MULTI_SEND_CALL_ONLY_ABI>', // Optional. Only needed with web3.js fallbackHandlerAbi: '<FALLBACK_HANDLER_ABI>', // Optional. Only needed with web3.js signMessageLibAbi: '<SIGN_MESSAGE_LIB_ABI>', // Optional. Only needed with web3.js createCallAbi: '<CREATE_CALL_ABI>', // Optional. Only needed with web3.js simulateTxAccessorAbi: '<SIMULATE_TX_ACCESSOR_ABI>' // Optional. Only needed with web3.js } } const safeSdk = await Safe.create({ ethAdapter, safeAddress, contractNetworks })
Returns a new instance of the Protocol Kit connected to a new Safe or a new Signer. The new connected signer can be passed via the ethAdapter
property while the new connected Safe can be passed using a safeAddress
or a predictedSafe
.
Connection of a deployed Safe using the safeAddress
property:
const safeSdk = await safeSdk.connect({ ethAdapter, safeAddress })
Connection of a not deployed Safe using the predictedSafe
property. Because Safes are deployed in a deterministic way, passing a predictedSafe
will allow to connect a Safe to the SDK with the Safe configuration:
import { PredictedSafeProps } from '@safe-global/protocol-kit'
const predictedSafe: PredictedSafeProps = {
safeAccountConfig,
safeDeploymentConfig
}
const safeSdk = await safeSdk.connect({ ethAdapter, predictedSafe })
-
The
isL1SafeMasterCopy
flagThere are two versions of the Safe contracts: Safe.sol that does not trigger events in order to save gas and SafeL2.sol that does, which is more appropriate for L2 networks.
By default
Safe.sol
will be only used on Ethereum Mainnet. For the rest of the networks where the Safe contracts are already deployed, theSafeL2.sol
contract will be used unless you add theisL1SafeMasterCopy
flag to force the use of theSafe.sol
contract.const safeSdk = await Safe.connect({ ethAdapter, safeAddress, isL1SafeMasterCopy: true })
-
The
contractNetworks
propertyIf the Safe contracts are not deployed to your current network, the
contractNetworks
property will be required to point to the addresses of the Safe contracts previously deployed by you.import { ContractNetworksConfig } from '@safe-global/protocol-kit' const chainId = await ethAdapter.getChainId() const contractNetworks: ContractNetworksConfig = { [chainId]: { safeMasterCopyAddress: '<MASTER_COPY_ADDRESS>', safeProxyFactoryAddress: '<PROXY_FACTORY_ADDRESS>', multiSendAddress: '<MULTI_SEND_ADDRESS>', multiSendCallOnlyAddress: '<MULTI_SEND_CALL_ONLY_ADDRESS>', fallbackHandlerAddress: '<FALLBACK_HANDLER_ADDRESS>', signMessageLibAddress: '<SIGN_MESSAGE_LIB_ADDRESS>', createCallAddress: '<CREATE_CALL_ADDRESS>', simulateTxAccessorAddress: '<SIMULATE_TX_ACCESSOR_ADDRESS>', safeMasterCopyAbi: '<MASTER_COPY_ABI>', // Optional. Only needed with web3.js safeProxyFactoryAbi: '<PROXY_FACTORY_ABI>', // Optional. Only needed with web3.js multiSendAbi: '<MULTI_SEND_ABI>', // Optional. Only needed with web3.js multiSendCallOnlyAbi: '<MULTI_SEND_CALL_ONLY_ABI>', // Optional. Only needed with web3.js fallbackHandlerAbi: '<FALLBACK_HANDLER_ABI>', // Optional. Only needed with web3.js signMessageLibAbi: '<SIGN_MESSAGE_LIB_ABI>', // Optional. Only needed with web3.js createCallAbi: '<CREATE_CALL_ABI>', // Optional. Only needed with web3.js simulateTxAccessorAbi: '<SIMULATE_TX_ACCESSOR_ABI>' // Optional. Only needed with web3.js } } const safeSdk = await Safe.connect({ ethAdapter, safeAddress, contractNetworks })
Returns the address of the current SafeProxy contract.
const safeAddress = await safeSdk.getAddress()
Returns the Safe Master Copy contract version.
const contractVersion = await safeSdk.getContractVersion()
Returns the list of Safe owner accounts.
const ownerAddresses = await safeSdk.getOwners()
Returns the Safe nonce.
const nonce = await safeSdk.getNonce()
Returns the Safe threshold.
const threshold = await safeSdk.getThreshold()
Returns the chainId of the connected network.
const chainId = await safeSdk.getChainId()
Returns the ETH balance of the Safe.
const balance = await safeSdk.getBalance()
Returns the enabled Safe Guard or 0x address if no guards are enabled.
const guardAddress = await safeSdk.getGuard()
Returns the list of addresses of all the enabled Safe Modules.
const moduleAddresses = await safeSdk.getModules()
Checks if a specific Safe Module is enabled for the current Safe.
const isEnabled = await safeSdk.isModuleEnabled(moduleAddress)
Checks if a specific address is an owner of the current Safe.
const isOwner = await safeSdk.isOwner(address)
Returns a Safe transaction ready to be signed by the owners and executed. The Protocol Kit supports the creation of single Safe transactions but also MultiSend transactions.
-
Single transactions
This method can take an object of type
SafeTransactionDataPartial
that represents the transaction we want to execute (once the signatures are collected). It accepts some optional properties as follows.import { SafeTransactionDataPartial } from '@safe-global/safe-core-sdk-types' const safeTransactionData: SafeTransactionDataPartial = { to, data, value, operation, // Optional safeTxGas, // Optional baseGas, // Optional gasPrice, // Optional gasToken, // Optional refundReceiver, // Optional nonce // Optional } const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
-
MultiSend transactions
This method can take an array of
MetaTransactionData
objects that represent the multiple transactions we want to include in our MultiSend transaction. If we want to specify some of the optional properties in our MultiSend transaction, we can pass a second argument to thecreateTransaction
method with theSafeTransactionOptionalProps
object.const safeTransactionData: MetaTransactionData[] = [ { to, data, value, operation // Optional }, { to, data, value, operation // Optional } // ... ] const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
This method can also receive the
options
parameter to set the optional properties in the MultiSend transaction:const safeTransactionData: MetaTransactionData[] = [ { to, data, value, operation // Optional }, { to, data, value, operation // Optional } // ... ] const options: SafeTransactionOptionalProps = { safeTxGas, // Optional baseGas, // Optional gasPrice, // Optional gasToken, // Optional refundReceiver, // Optional nonce // Optional } const safeTransaction = await safeSdk.createTransaction({ safeTransactionData, options })
In addition, the optional
callsOnly
parameter, which isfalse
by default, allows to force the use of theMultiSendCallOnly
instead of theMultiSend
contract when sending a batch transaction:const callsOnly = true const safeTransaction = await safeSdk.createTransaction({ safeTransactionData, options, callsOnly })
If the optional properties are not manually set, the Safe transaction returned will have the default value for each one:
operation
:OperationType.Call
(0) is the default value.safeTxGas
: The right gas estimation is the default value.baseGas
: 0 is the default value.gasPrice
: 0 is the default value.gasToken
: 0x address is the default value.refundReceiver
: 0x address is the default value.nonce
: The current Safe nonce is the default value.
Returns a Safe transaction ready to be signed by the owners that invalidates the pending Safe transaction/s with a specific nonce.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const rejectionTransaction = await safeSdk.createRejectionTransaction(safeTransaction.data.nonce)
Copies a Safe transaction.
const safeTransaction1 = await safeSdk.createTransaction({ safeTransactionData })
const safeTransaction2 = await copyTransaction(safeTransaction1)
Returns the transaction hash of a Safe transaction.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const txHash = await safeSdk.getTransactionHash(safeTransaction)
Signs a hash using the current owner account.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const txHash = await safeSdk.getTransactionHash(safeTransaction)
const signature = await safeSdk.signTransactionHash(txHash)
Signs a transaction according to the EIP-712 using the current signer account.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const signature = await safeSdk.signTypedData(safeTransaction)
Returns a new SafeTransaction
object that includes the signature of the current owner. eth_sign
will be used by default to generate the signature.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const signedSafeTransaction = await safeSdk.signTransaction(safeTransaction)
Optionally, an additional parameter can be passed to specify a different way of signing:
const signedSafeTransaction = await safeSdk.signTransaction(safeTransaction, 'eth_signTypedData')
const signedSafeTransaction = await safeSdk.signTransaction(safeTransaction, 'eth_sign') // default option.
Approves a hash on-chain using the current owner account.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const txHash = await safeSdk.getTransactionHash(safeTransaction)
const txResponse = await safeSdk.approveTransactionHash(txHash)
await txResponse.transactionResponse?.wait()
Optionally, some properties can be passed as execution options:
const options: Web3TransactionOptions = {
from, // Optional
gas, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const options: EthersTransactionOptions = {
from, // Optional
gasLimit, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const txResponse = await safeSdk.approveTransactionHash(txHash, options)
Returns a list of owners who have approved a specific Safe transaction.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const txHash = await safeSdk.getTransactionHash(safeTransaction)
const ownerAddresses = await safeSdk.getOwnersWhoApprovedTx(txHash)
Returns the Safe transaction to enable the fallback handler.
const safeTransaction = await safeSdk.createEnableFallbackHandlerTx(fallbackHandlerAddress)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = {
safeTxGas, // Optional
baseGas, // Optional
gasPrice, // Optional
gasToken, // Optional
refundReceiver, // Optional
nonce // Optional
}
const safeTransaction = await safeSdk.createEnableFallbackHandlerTx(fallbackHandlerAddress, options)
Returns the Safe transaction to disable the fallback handler.
const safeTransaction = await safeSdk.createDisableFallbackHandlerTx()
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createDisableFallbackHandlerTx(options)
Returns the Safe transaction to enable a Safe Guard.
const safeTransaction = await safeSdk.createEnableGuardTx(guardAddress)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = {
safeTxGas, // Optional
baseGas, // Optional
gasPrice, // Optional
gasToken, // Optional
refundReceiver, // Optional
nonce // Optional
}
const safeTransaction = await safeSdk.createEnableGuardTx(guardAddress, options)
Returns the Safe transaction to disable a Safe Guard.
const safeTransaction = await safeSdk.createDisableGuardTx()
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createDisableGuardTx(options)
Returns a Safe transaction ready to be signed that will enable a Safe Module.
const safeTransaction = await safeSdk.createEnableModuleTx(moduleAddress)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createEnableModuleTx(moduleAddress, options)
Returns a Safe transaction ready to be signed that will disable a Safe Module.
const safeTransaction = await safeSdk.createDisableModuleTx(moduleAddress)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createDisableModuleTx(moduleAddress, options)
Returns the Safe transaction to add an owner and optionally change the threshold.
const params: AddOwnerTxParams = {
ownerAddress,
threshold // Optional. If `threshold` is not provided the current threshold will not change.
}
const safeTransaction = await safeSdk.createAddOwnerTx(params)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createAddOwnerTx(params, options)
Returns the Safe transaction to remove an owner and optionally change the threshold.
const params: RemoveOwnerTxParams = {
ownerAddress,
newThreshold // Optional. If `newThreshold` is not provided, the current threshold will be decreased by one.
}
const safeTransaction = await safeSdk.createRemoveOwnerTx(params)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createRemoveOwnerTx(params, options)
Returns the Safe transaction to replace an owner of the Safe with a new one.
const params: SwapOwnerTxParams = {
oldOwnerAddress,
newOwnerAddress
}
const safeTransaction = await safeSdk.createSwapOwnerTx(params)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createSwapOwnerTx(params, options)
Returns the Safe transaction to change the threshold.
const safeTransaction = await safeSdk.createChangeThresholdTx(newThreshold)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
This method can optionally receive the options
parameter:
const options: SafeTransactionOptionalProps = { ... }
const safeTransaction = await safeSdk.createChangeThresholdTx(newThreshold, options)
Checks if a Safe transaction can be executed successfully with no errors.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const isValidTx = await safeSdk.isValidTransaction(safeTransaction)
Optionally, some properties can be passed as execution options:
const options: Web3TransactionOptions = {
from, // Optional
gas, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const options: EthersTransactionOptions = {
from, // Optional
gasLimit, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const isValidTx = await safeSdk.isValidTransaction(safeTransaction, options)
Executes a Safe transaction.
const safeTransactionData: SafeTransactionDataPartial = {
// ...
}
const safeTransaction = await safeSdk.createTransaction({ safeTransactionData })
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
Optionally, some properties can be passed as execution options:
const options: Web3TransactionOptions = {
from, // Optional
gas, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const options: EthersTransactionOptions = {
from, // Optional
gasLimit, // Optional
gasPrice, // Optional
maxFeePerGas, // Optional
maxPriorityFeePerGas // Optional
nonce // Optional
}
const txResponse = await safeSdk.executeTransaction(safeTransaction, options)