Transaction Relay Service

This service allows us to have owners of the Safe contract that don’t need to hold any ETH on those owner addresses. How is this possible? The transaction relay service acts as a proxy, paying for the transaction fees and getting it back due to the transaction architecture we use.

Our target user hold crypto in a centralized exchange (or on another Ethereum address) and wants to move it to a secure account. We don’t want the user to trust us, for moving the funds and deploying the smart contract on their behalf. We on the other side want to prevent users from spamming our services, there shouldn’t be a need to trust the user either. The process for this is descriped in the contracts deployment section.

Show on GitHub

Show on Swagger

Flows

Safe creation flowchart

Transaction execution flowchart

API Endpoints


Types

address - hexadecimal string which represents an address with checksum and 0x prefix stringified-int - stringified int, base 10


/safes/ POST

Creates new Safe Creation Transaction with random signature, generated by user and server, so no one knows the private key of the deployer address.

Note: We don’t use a Chain ID to facilitate testing on different chains (cf. EIP-155. We don’t need the replay protection, because no one knows the private key. The PayingProxy is used in this process. Furthermore, we use “fast” from our gas station. The First version will only support ETH as gasToken. Therefore the payment will be returned in Wei.

More info about the signature values in appendix F of the Ethereum Yellow Paper.

Request:

{
    "owners": ["<address>"],
    "threshold": "int", // min 1
    "s": "stringified-int", // (0 "< s "< secp256k1n / 2 + 1)
    "paymentToken": "<address>", // optional, address of ERC20 token that should be used for paying the contract deployment
}

Returns

HTTP 201
{
    "signature": {
        "r": "<stringified-int>", // (0 "< r "< secp256k1n)
        "s": "<stringified-int>", // (0 "< s "< secp256k1n / 2 + 1)
        "v": "<int>" // (27 or 28)
    },
    "tx" : {
        "from": "<string>",
        "value":  "<stringified-int>", // (wei) Will always be 0
        "data": "<string>",
        "gas": "<stringified-int>",
        "gasPrice": "<stringified-int>", // (wei)
        "nonce": 0
    },
    "safe": "<address>",
    "deployer": "<address>",
    "funder": "<address>",
    "payment": "<stringified-int>", // it’s what the service gets as refund
    "paymentToken": "<address>", // if no gasToken was specified in the request this will be address(0) for ETH
}
HTTP 400 not valid values submitted

Note: Atomic operation, many values of s are invalid which are generated by the server.

Clients should verify the server’s response with the following process:

  1. Verify that the s that you provided in the request matches the s returned in the response.
  2. Verify that tx.data matches the bytecode of the Gnosis Safe Proxy contract with the correct owners and threshold. The value and nonce of the transaction should be zero.
  3. Hash the transaction object (tx) and recover the account with the provided signature. The resulting address should match the deployer.
  4. Compute the Safe address with the deployer address and nonce=0. The resulting address should match the provided safe address.

If all checks pass, then the transaction and Safe address are valid and the user can transfer at least the payment amount of ETH (if paymentToken is address 0x0) or the corresponding amount of paymentToken tokens (if the paymentToken is a valid token address) to the Safe address. Please take a look at the /tokens/ endpoint to see which tokens are accepted for payment by our service.

Otherwise, the response has error or it is compromised, and it should not be used any further.


For Step 2, in order to check if the tx.data is valid (ie.: proxy creation data with the correct parameters):

  1. Check that the initial portion of tx.data corresponds to the PayingProxy contract data (smart contract source code).
    1. See Solidity Contracts Metadata for more details.
  2. Check that the payload immediately after the PayingProxy contract data matches the constructor parameters:
    1. Master Copy (address) – address of the master copy which contains the smart contract’s logic
    2. Initializer (bytes) – data used to perform a delegate call (eg.: for initial setup of the proxy)
    3. Funder (address) – address that should be paid for covering the gas costs of the deployment
    4. Payment Token (address) – ERC20 token address used for paying for the deployment of the proxy. Address 0x0 if the payment will be made in ETH.
    5. Payment (uint256) – amount of ETH (if the payment token is 0x0) or of the token at the payment token address that should be transfered to the proxy in order to deploy it.

Important: The above validation steps describe how you can validate the PayingProxy which performs delegate calls to any smart contract specified in the Master Copy. In the context of the Gnosis Safe and our relay services:

  • The Master Copy should be an address of a valid deployment of the GnosisSafe
  • The initializer is the ABI encoded call to GnosisSafe.setup(...) (smart contract function).
  • The funder is the Ethereum address of our relay service (or any other relay that will deploy the Gnosis Safe).

/safes/<address>/ GET

Get info about a deployed Safe querying the blockchain.

Returns:

HTTP 200
{
    "address": "<address>",
    "nonce":  "<int>",
    "threshold": "<string>",
    "owners": ["<address>"],
}

/safes/<address>/funded/ PUT

Signal funds were transferred, start Safe creation

Returns:

HTTP 202

Note: Creation has 2 txs and a check/. This is done asynchronously through a queue.


/safes/<address>/funded/ GET

Get info about Safe’s funding status

Returns:

HTTP 200
{
    "safeFunded": "<boolean>",  # Safe has enough balance to start the deploying
    "deployerFunded":  "<boolean>",  # Deployer was funded and confirmations awaited
    "deployerFundedTxHash": "<string>",  # Deployer funding tx hash
    "safeDeployed": "<boolean>",  # Safe was finally deployed
    "safeDeployedTxHash": "<string>"  # Safe tx was sent to the network
}

/gas-station/ GET

Similar to ETH Gas Station but with reliable availability and sufficient rate limits

Returns:

HTTP 200
{
    "safeLow": "<stringified-int>", // wei
    "standard": "<stringified-int>", // wei
    "fast": "<stringified-int>", // wei
    "fastest": "<stringified-int>", // wei
}

/safes/<address>/transactions/estimate/ POST

Estimates the gas and gasPrice for the requested Safe transaction. Safe contract needs to exist previously. To estimate transaction cost, use the following formula:

gasCosts = (safeTxGas + dataGas) * gasPrice

Request:

{
    "to": "<address>",
    "value": "<stringified-int>", // wei
    "data": "<string>", // prefixed or unprefixed hex string
    "operation": "<integer>", // enumerated from here (0 - call, 1 - delegateCall, 2 - create)
    "gasToken": "<address>", // optional, address of ERC20 token that should be used for gas payment
    "nonce": "<integer>" // nonce of the last tx sent for execution
}

Returns:

HTTP 200
{
    "safeTxGas": "<integer>",
    "dataGas": "<integer>",
    "gasPrice": "<integer>",
    "gasToken": "<address>", // if no gasToken was specified in the request this will be address(0) for ETH
}

/safes/<address>/transactions/ POST

Allows to send and pay transactions via the Transaction Relay Service. The Safe contract the tx is directed to must have enough ETH to pay tx fees and be created through the tx relay service. Safe contract needs to exist previously.

Request:

{
    "to": "<address>",
    "value": "<stringified-int>", // wei
    "data": "<string>", // prefixed or unprefixed hex string
    "operation": "<integer>", // enumerated from here
    "signatures": [{ 
        "v": "<integer>",
        "r": "<string>",
        "s": "<string>"
    }, ...], // Sorted lexicographically by owner address (comparision done on the number value of an address)
    "safeTxGas": "<stringified-int>",
    "dataGas": "<stringified-int>",
    "gasPrice": "<stringified-int>",
    "nonce": "<stringified-int>",
    "gasToken": "<address>",
}

Returns:

HTTP 201
{
	"transactionHash": "<string>"
}

Note: Atomic operation.

/tokens/ GET

Returns a paginated list of tokens. Each token has the ERC20 information (address, name, symbol, decimals) and if available additional meta information to the token (icon, website …). Furthermore tokens can be marked to be shown to the user by default.

Notes:

  • Currently token info is retrieved from etherscan

Query params:

Besides pagination:

  • search: Search words in name, symbol and description.
  • name, symbol and address: Do an exact filtering based on that parameters.
  • default: If 1 just show tokens marked to be shown by default.
  • gas: If 1 just show tokens that can be used to pay for gas.
  • decimals__lt and decimals__gt: Filter based on tokens with decimals less than or greater than.

Response

Returns HTTP 200
{
  "count":432,
  "next":"${host}:${port}/api/v1/tokens/?limit=100&offset=200",
  "previous":"${host}:${port}/api/v1/tokens/?limit=100",
  "results": [
    {
      "address": <hex encoded checksummed address (0xaBc1....),
      "name": <string>,
      "symbol": <string>,
      "decimals": <int>,
      "logoUri": <string>,
      "websiteUri": <string>,
      "default": <bool>,
      "gas": <bool>,  // If token can be used as gas token
    }
  ]
}