We assume you have set up your nodejs environment and you simply need to plug-in our library.
- Install. You need to install the NPM pacakge:
npm i @anydotcrypto/metatransactions --save-dev
- Environment. Decide which network to use.
You will need to import ChainID into your code:
import { ChainID } from "@anydotcrypto/metatransactions";
Our library currently supports MAINNET or ROPSTEN.
// Blockchain ID (Mainnet = 3)
ChainID.MAINNET;
ChainID.ROPSTEN;
The Gnosis Safe library only supports NONCE replay protection. Check out our wallet contracts for more exotic replay protection.
- Let's create the meta-transaction library with your preferred options!
To instantiate the Gnosis Safe forwarder:
const signer = Wallet.Mnemonic("").connect(provider);
const gnosisForwarder = new GnosisSafeForwarder(
ChainID.ROPSTEN,
signer,
signer.address
);
This links the signer's wallet to their gnosis safe contract.
Important: Our library can authorise a meta-transaction if the Gnosis Safe contract does not yet exist (e.g. not deployed). Using the MultiSend contract, it is possible to meta-deploy the Gnosis Safe contract and then execute the first meta-transaction in a single Ethereum Transaction. So there is no waiting/setup process.
We will show how to authorise a meta-transaction using the Gnosis Safe forwarder.
The Echo smart contract is used for our example:
pragma solidity ^0.6.2;
contract Echo {
event Broadcast(address signer, string message);
function submit(string memory _message) public
{
emit Broadcast(msg.sender, _message);
}
}
The full example is available and it covers:
- Checking the gnosis safe contract exists before deploying it.
- Deploying the Echo Contract via the gnosis safe contract
- Sending a meta-transaction to the Echo Contract via the gnosis safe contract.
Let's set up our forwarder and signer:
const user = Wallet.Mnemonic("");
const relayer = Wallet.Mnemonic("");
const gnosisForwarder = new GnosisSafeForwarder(
ChainID.ROPSTEN,
user,
user.address
);
We can deploy the Gnosis Safe contract.
const isGnosisSafeDeployed = await gnosisForwarder.isWalletDeployed();
if (!isGnosisSafeDeployed) {
const minimalTx = await gnosisForwarder.getWalletDeployTransaction();
// For our example we mimic the relayer API with a relayer wallet.
const gnosisSafeTx = await relayer.sendTransaction({
to: minimalTx.to,
data: minimalTx.data,
});
// Wait 1 block confirmation
const gnosisSafeReceipt = await gnosisSafeTx.wait(1);
}
Supply the following information:
- To contract's address (required)
- Data the function name and its arguments (required)
- Value to be sent (in wei) - (optional)
Once you have settled on the message to echo, you can use this code sample to authorise the meta-transaction:
// Fetch the contract and the data.
const echo = new EchoFactory(user).attach("");
const data = echo.interface.functions.submit.encode(["hello"]);
// Sign the meta transaction & encode it.
const metaTx = await gnosisForwarder.signMetaTransaction({
to: echo.address,
value: "0",
data: data,
});
const submitTicketTx = await relayer.sendTransaction({
to: metaTx.to,
data: metaTx.data,
});
const submitTicketReceipt = await submitTicketTx.wait(1);
Easy right? You have just deployed the gnosis safe contract and sent a meta-transaction via the gnosis safe contract. Our library has taken care of the replay protection & constructing the transaction data for you. As well, its essentially the same workflow as our proxy contract flow.
Well done!
We take this opportunity to cover each function in the library.
You can use the factory to set up a new forwarder. It requires you to select the ChainID. We use the ChainID in the contract address salt to fix the network replay attack problem with Gnosis Safe. YOU WILL HAVE A DIFFERENT WALLET ADDRESS ON EVERY NETWORK. It only supports NONCE replay protection.
const gnosisForwarder = new GnosisSafeForwarder(
ChainID.ROPSTEN,
signer,
signer.address
);
Once you have instantiated the forwarder, then you can access the following properties:
const gnosisSafeAddress = gnosisSafeForwarder.address;
const signer = gnosisSafeForwarder.signer;
In Gnosis Safe, there is a one-to-one mapping for the setup data that includes the signer's key used to create the gnosis safe. We use a basic setup of a single-signer gnosis safe with no pre-installed modules. The library will automatically compute the address and make it available via gnosisSafeForwarder.address
. Furthermore, the Signer
is accessible via gnosisSafeForwarder.signer
.
There are two helper functions:
const isGnosisSafeDeployed = await gnosisSafeForwarder.isWalletDeployed();
const minimalTx = await gnosisSafeForwarder.getWalletDeployTransaction();
The former lets you check if the gnosis safe contract is already deployed. The latter prepares a meta-transaction that can be packed into an Ethereum Transaction to deploy the gnosis safe contract. Note the MinimalTx
only contains the fields to, data
.
There is a single function for authorising a meta-transaction:
const echoAddress = "0x...";
const data = echoContract.interface.functions.sendMessage.encode([
"any.sender is nice",
]);
const metaTx = await gnosisSafeForwarder.signMetaTransaction({
to: echoAddress,
data: data,
value: "0",
});
It returns a MinimalTx
that only contains the fields to, data
which can be packed into an Ethereum Transaction. This takes care of preparing the replay protection & wrapping the call so it can be processed by the gnosis safe contract.
Note there is an additional callType
field that can be used to decide if it is a call
or a delegatecall
. We only discuss call and advanced users can look at the contract on how to use delegatecall.
To deploy use the signMetaTransaction function but replace the to
argument with a salt
:
const initCode = new EchoFactory(user).getDeployTransaction().data!;
const value = "0";
const salt = "0x123";
const metaDeploy = await gnosisSafeForwarder.signMetaTransaction({
initCode,
value,
salt
});
The signMetaTransaction
function prepares a MinimalTx
for the deployment. Again it only contains a to, value, data
that can be packed in the Ethereum Transaction. In reality, it is using delegatecall
from the gnosis safe contract into a global deployer contract and then deploy the smart contract.
The computeAddressForDeployedContract
computes the address for the contract. It just requires the initCode
and the salt
used for the deployment.
You need to prepare a list of transactions to use in the batch:
const metaTxList = [
{
to: msgSenderCon.address,
value: 0,
data: data,
revertOnFail: true,
},
{
data: initCode,
salt: "v0.1",
value: 0,
},
{
to: echoCon.address,
value: 0,
data: data,
revertOnFail: false,
},
];
An additional feature is revertOnFail
which lets you decide if the entire batch of transactions should revert if the meta-transaction fails. Again, we omit CallType
as it should only be used by advanced users and most meta-transactions only require the .call
functionality.
Now you can batch the transactions:
const minimalTx = await gnosisSafeForwarder.signMetaTransaction(metaTxList);
The MinimalTx
contains the fields to, data
that can be packed into an Ethereum Transaction. Each meta-transaction is processed in order by the proxy account contract.
You can decode a metatransaction into it's consutituent parts by using the decodeTx or decodeBatchTx functions.
For a single tx:
const echoAddress = "0x...";
const data = echoContract.interface.functions.sendMessage.encode([
"any.sender is nice",
]);
const metaTx = await gnosisSafeForwarder.signMetaTransaction({
to: echoAddress,
data: data,
value: "0",
});
const forwardFunctionArguments = gnosisSafeForwarder.decodeTx(metaTx.data);
Or for a batch tx:
const echoAddress = "0x...";
const data = echoContract.interface.functions.sendMessage.encode([
"any.sender is nice",
]);
const metaTx = await proxyAccount.signMetaTransaction([
{
to: echoAddress,
data: data,
value: "0",
},
]);
const forwardFunctionArguments = proxyAccount.decodeBatchTx(metaTx.data);