Search…
Initiate Transactions
Initiate and sign transactions with the Safe transaction service
The Safe transaction service keeps track of transactions sent via Gnosis Safe contracts. It uses events and transaction tracing to index the txs. It is used by Gnosis Safe UIs (Web interface/iOS/Android clients) to retrieve transaction information and to exchange off-chain signatures.
Transactions are detected in an automatic way, so there is no need of informing the service about the transactions as in previous versions of the Transaction Service.
Transactions can also be sent to the service to allow offchain collecting of signatures or informing the owners about a transaction that is pending to be sent to the blockchain.
Source code can be found in https://github.com/gnosis/safe-transaction-service​
Gnosis is currently running the backend for these Ethereum networks:

Proposing transactions

This tutorial will teach you how to propose transactions to the transaction service, there are 2 reasons you would want to do that:
    The transactions appear on the UIs, so you can craft custom transactions that can be signed and send by any of the owners in any of the UIs. Please note: Transactions submitted as a non-owner will not show up on the official Safe interfaces unless you previously added a delegate. There is a separate tutorial on how to add a delegate to your Safe.
    As a way to collect offchain signatures (instead of calling approveHash method on the Safe contract with every owner) and save gas.

Proposing transactions using Javascript

If you can code in Javascript, you can edit this example to fit your needs: https://gist.github.com/rmeissner/0fa5719dc6b306ba84ee34bebddc860b​

Fields required for a Safe Transaction

This is the JSON structure of a Safe Transaction. All the ethereum addresses need to be checksummed:
1
{
2
"to": "<checksummed address>",
3
"value": 0, // Value in wei
4
"data": "<0x prefixed hex string>",
5
"operation": 0, // 0 CALL, 1 DELEGATE_CALL
6
"gasToken": "<checksummed address>", // Token address (hold by the Safe) to be used as a refund to the sender, if `null` is Ether
7
"safeTxGas": 0, // Max gas to use in the transaction
8
"baseGas": 0, // Gast costs not related to the transaction execution (signature check, refund payment...)
9
"gasPrice": 0, // Gas price used for the refund calculation
10
"refundReceiver": "<checksummed address>", //Address of receiver of gas payment (or `null` if tx.origin)
11
"nonce": 0, // Nonce of the Safe, transaction cannot be executed until Safe's nonce is not equal to this nonce
12
"contractTransactionHash": "string", // Contract transaction hash calculated from all the field
13
"sender": "<checksummed address>", // Owner of the Safe proposing the transaction. Must match one of the signatures
14
"signature": "<0x prefixed hex string>", // One or more ethereum ECDSA signatures of the `contractTransactionHash` as an hex string
15
"origin": "string" // Give more information about the transaction, e.g. "My Custom Safe app"
16
}
Copied!
Some fields are related to the refund payment (sender of the transaction gets a refund of the estimated cost of the transaction by the Safe). If this feature is not needed you can set these fields like this:
1
{
2
"gasToken": null,
3
"baseGas": 0,
4
"gasPrice": 0,
5
"refundReceiver": null
6
}
Copied!

Preparing a transaction

Through this tutorial we will prepare a custom transaction for a Rinkeby Safe. Let's say we want to send 1 ether to 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 and we don't need refund for the sender. Safe is 0x03c6dda6C17353e821bCb59e419f961a30BC7F78 and one of the owners is 0x6a2EB7F6734F4B79104A38Ad19F1c4311e5214c8 with a private key of 0x66e91912f68828c17ad3fee506b7580c4cd19c7946d450b4b0823ac73badc878

Get current Safe nonce

1
{
2
"address": "0x03c6dda6C17353e821bCb59e419f961a30BC7F78",
3
"nonce": 5,
4
"threshold": 1,
5
"owners": [
6
"0x6a2EB7F6734F4B79104A38Ad19F1c4311e5214c8",
7
],
8
"masterCopy": "0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F",
9
"modules": [ ],
10
"fallbackHandler": "0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44",
11
"version": "1.1.1"
12
}
Copied!
Current nonce is 5.

Getting gas estimation

If it's available on the network, you can use the Safe Relay Service to get the gas estimation for the transaction. Currently Safe Relay is depoyed on the following networks:
For estimating, relay requires a small subset of the Safe Transaction:
1
{
2
"to": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
3
"value": 1000000000000000000,
4
"data": null,
5
"operation": 0, // 0 CALL, 1 DELEGATE_CALL
6
"gasToken": null
7
}
Copied!
1
{
2
"safeTxGas": "43845",
3
"baseGas": "54960",
4
"dataGas": "54960",
5
"operationalGas": "0",
6
"gasPrice": "16666666667",
7
"lastUsedNonce": 4,
8
"gasToken": "0x0000000000000000000000000000000000000000",
9
"refundReceiver": "0x07fd2865c8DE725B4e1f4E2B72E5c654baA7c4b3"
10
}
Copied!
Ignore most of the fields as we will not use the Relay and we don't need the refund parameters it provides. Just get safeTxGas. You can also get nonce from here (adding + 1) and skip the previous step.
If there's no available Relay for the Ethereum network you are using, you can call requiredTxGas on the contract, or just provide 0 so a regular estimation is done.

Get the contractTransactionHash

Calling the contract

Contract transaction hash is calculated using the structured data hashing defined on EIP712. You can take a look on how is calculated on the contract. You can call getTransactionHash on your Safe contract to get the hash of the transaction. In the fields where an address is expected but you don't want to use one (like refundReceiver) use an address with all zeroes (address(0) in Solidity).
Transaction hash would be 0x1ed9d878f89585977e98425d5cedf51027c041e414bb471d64519f8f510bb555

Lazy way

This way is not recommended, as it requires you to trust the server and you can easily sign a malicious transaction. For this way to work, just set a random contractTransactionHash and signature, so the server complains with the expected contractTransactionHash.
The transaction would be:
1
{
2
"to": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
3
"value": 1000000000000000000,
4
"data": null,
5
"operation": 0, // 0 CALL
6
"gasToken": null,
7
"safeTxGas": 43845,
8
"baseGas": 0,
9
"gasPrice": 0,
10
"refundReceiver": null,
11
"nonce": 5,
12
"contractTransactionHash": "0xd112233445566778899aabbccddff00000000000000000000000000000000000", // We still don't know the transaction hash
13
"sender": "0x6a2EB7F6734F4B79104A38Ad19F1c4311e5214c8",
14
"signature": "0x000000000000000000000000a935484ba4250c446779d4703f1598dc2ea00d12000000000000000000000000000000000000000000000000000000000000000001", // As we don't know the `contractTransactionHash` we cannot sign it yet
15
"origin": "Tx Service Tutorial"
16
}
Copied!
You will receive an error:
1
{
2
"nonFieldErrors": [
3
"Contract-transaction-hash=0x1ed9d878f89585977e98425d5cedf51027c041e414bb471d64519f8f510bb555 does not match provided contract-tx-hash=0xd112233445566778899aabbccddff00000000000000000000000000000000000"
4
]
5
}
Copied!
Transaction hash would be 0x1ed9d878f89585977e98425d5cedf51027c041e414bb471d64519f8f510bb555

Sign

We should then use 0x1ed9d878f89585977e98425d5cedf51027c041e414bb471d64519f8f510bb555 as the contractTransactionHash and sign it with our private key. You can use Python3 for that:
1
pip install eth_account
2
python3
Copied!
1
from eth_account import Account
2
from hexbytes import HexBytes
3
contract_transaction_hash = HexBytes('0x1ed9d878f89585977e98425d5cedf51027c041e414bb471d64519f8f510bb555')
4
account = Account.from_key('0x66e91912f68828c17ad3fee506b7580c4cd19c7946d450b4b0823ac73badc878')
5
signature = account.signHash(contract_transaction_hash)
6
print(signature.signature.hex())
Copied!
Signature is 0xc0df6a1b659d56d3d23f66cbd1c483467ea68a428fea7bbbe0a527d43d8681f616af33344035f36c08218718480374dada0fe6cdb266d0182a4225d0e9c227181b.
More than one signature (for different owners) can be sent. They should be appended and the result should be a hex string 0x prefixed. More information on Gnosis Safe contract signatures.
Our transaction is complete now:
1
{
2
"to": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
3
"value": 1000000000000000000,
4
"data": null,
5
"operation": 0,
6
"gasToken": null,
7
"safeTxGas": 500000, // Random estimation
8
"baseGas": 0,
9
"gasPrice": 0,
10
"refundReceiver": null,
11
"nonce": 5,
12
"contractTransactionHash": "0x1ed9d878f89585977e98425d5cedf51027c041e414bb471d64519f8f510bb555",
13
"sender": "0x6a2EB7F6734F4B79104A38Ad19F1c4311e5214c8",
14
"signature": "0xc0df6a1b659d56d3d23f66cbd1c483467ea68a428fea7bbbe0a527d43d8681f616af33344035f36c08218718480374dada0fe6cdb266d0182a4225d0e9c227181b",
15
"origin": "Tx Service Tutorial"
16
}
Copied!
Last modified 14d ago