Skip to content

Transaction app types and mapping from json to UI layer

Jose Alvarez edited this page Jun 16, 2020 · 11 revisions
  1. Transaction DTO (backend)

  2. Data layer mapping: (https://github.com/gnosis/safe/issues/324)

The following was taken directly from the issue #324. As mentioned in the feedback, the clients should not implement as of this writing, the "Backup Plan".

tx.type is MODULE_TRANSACTION

For now, we ignore this type of transaction and not show it in the user interface.

tx.type is ETHEREUM_TRANSACTION

These are external transactions and they are already executed.

For each such transaction, we look into a tx.transfers array.

    if transfers is not empty
        each transfer is a new UI cell
    if transfers is empty or null
        if data is not nulll 
            then it is custom transaction
        else data is null, 
            so it is ETH transfer.
            "tx.value" can be null or absent - then treat it as 0.

Each transfer represents a separate user interface row in the transaction list. A transfer may be incoming, or outgoing - you need to see at the transfer.to and transfer.from addresses and see if one of them is a tx.safe address.

transfer.to is a recipient address, and transfer.from is a sender address of that transfer.

tx.type is MULTISIG_TRANSACTION

These are transactions originating from the user's safe. Each such transaction is represented by a single row in a transactions list user interface.

They include transactions that are already executed (tx.isExecuted is true), transactions that were not executed (isExecuted is false) but superseeded by other executed transactions (we call them cancelled transactions), and transactions that are not yet executed.

Due to the technical details of the working with the blockchain data structures, for now, we must not rely on the transfers array that we get in the transaction object, but rather classify the transaction to one of the user interface types based on the transaction's properties.

That is, we ignore the tx.transfers for the transactions with tx.type MULTISIG_TRANSACTION

ETH transfer transactions

All conditions must be met:

    tx.data == null
    tx.operation == 0
    tx.value == any

These will be ether transfers. The tx.to is recipient, the tx.safe is sender.

In all other cases the tx.data is not null, it is present.

The data is a hex-string serialized binary data. If the data is present, we expect backend to also provide the tx.dataDecoded object with the details of the method call that this transaction is invoking.

if tx.dataDecoded == null or empty, then the transaction is Custom transaction

Safe Setting Changes

All conditions must be met:

    tx.to == safe address
    tx.operation == 0
    tx.dataDecoded.method is one of:
        changeMasterCopy
        setFallbackHandler
        addOwnerWithThreshold
        removeOwner
        swapOwner
        changeThreshold
        enableModule
        disableModule

Every interaction with the aforementioned methods of the Safe smart contract requires the smart contract itself to execute it, that is why we check that the tx.to is the tx.safe itself.

ERC20 Transfers

All conditions must be met:

    tx.operation == 0
    tx.contractInfo.type == "ERC20"
    tx.dataDecoded.method is one of:
        transfer
        transferFrom

If the tx.contractInfo is not present, treat the transaction as Custom transaction

Because this is an etherum transaction directed at the token smart contract, the tx.to will be the token contract's address, and not the recipient of tokens.

To get the recipient of tokens, as well as amount transferred, you need to look at the parameters of the methods.

    function transfer(address _to, uint256 _value) public returns (bool success)

For this method, the tx.safe is the sender, the _to parameter address is the recipient, and the _value is the amount of tokens.

To get the information about the token's symbol, decimals, name, etc. we look at the tx.contractInfo object.

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

For this method, the _from parameter address is the sender, the _to parameter address is the recipient, and the value is the amount in the token's basic units.

To get the information about the token's symbol, decimals, name, etc. we look at the tx.contractInfo object.

ERC721 Transfers

All conditions must be met:

    tx.operation == 0
    tx.contractInfo.type == "ERC721"
    tx.dataDecoded.method is one of:
        safeTransferFrom
        transferFrom

If the tx.contractInfo is not present, treat the transaction as Custom transaction

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

There are 3 possible methods. For each of them, the _from parameter is sender, the _to is recipient, the amount is 1, since this transfers only 1 token with specific _tokenId.

Custom transaction

Everything else is treated as a Custom transaction.

The tx.to is recipient, the tx.data is the data sent along with the transaction, the tx.value is ETH value transferred with the transaction (can be positive number).

  1. UI layer mapping: (each type is grouped by queued/history)

    • Transfer

      • transfer direction: if(Transfer.recipient == currentSafe) Incoming else Outgoing
    • SettingsChange

      • methodName = SettingsChange.dataDecoded.method, define human readable assets for each method in settingMethodNames = ["setFallbackHandler", "addOwnerWithThreshold", "removeOwner", "swapOwner", "changeThreshold", "changeMasterCopy", "enableModule", "disableModule"]
    • ChangeMasterCopy (specification of SettingsChange)

      • SettingsChange.dataDecoded.method == "changeMasterCopy", get version name string from hardcoded asset (SettingsChange.dataDecoded.params[0].value)
    • Custom

      • n/a

Status calculation

precondition: Safe info needs to be available. Should be fetched on the first page of transaction data and re-triggered on user refresh:

  • queued = status is one of {pending, waitingForConfirmation, waitingFroExecution} sorted by nonce ascending, creation date descending. NOTE: since these are outgoing transactions, they must have nonce.
  • Pending: Submitted from device, therefore not supported yet
  • WaitingForConfirmation
  • condition: transactionDto.isExecuted != true && transactionDto.nonce >= safe.nonce && transactionDto.confirmations.size < safe.threshold
  • WaitingForExecution
  • condition: transactionDto.isExecuted != true && transactionDto.nonce >= safe.nonce && transactionDto.confirmations.size >= safe.threshold
  • history = status is one of {success, failed, cancelled} sorted by execution date (if null, then created date), descending
  • Success
  • condition: transactionDto.isExecuted == true && transactionDto.isSuccessful == true
  • Failed
  • condition: transactionDto.isExecuted == true && transactionDto.isSuccessful != true
  • Cancelled
  • condition: transactionDto.isExecuted != true && transactionDto.nonce < safe.nonce
  1. Pagination No dodgy business

  2. Backend Assumptions: transfers - logical transfers inferred from the transaction

  • ETHER_TRANSFER : ETH is transferred (data = null)
  • ERC20_TRANSFER : some token
  • ERC721_TRANSFER : some other token
  • UNKNOWN : might be a sh*t token
Clone this wiki locally