class SafeMultisigTransactionResponseSerializer(SafeMultisigTxSerializerV1):
    execution_date = serializers.DateTimeField()
    submission_date = serializers.DateTimeField(source='created')  # First seen by this service
    modified = serializers.DateTimeField()
    block_number = serializers.SerializerMethodField()
    transaction_hash = Sha3HashField(source='ethereum_tx_id')
    safe_tx_hash = Sha3HashField()
    executor = serializers.SerializerMethodField()
    value = serializers.CharField()
    is_executed = serializers.BooleanField(source='executed')
    is_successful = serializers.SerializerMethodField()
    gas_price = serializers.CharField()
    eth_gas_price = serializers.SerializerMethodField()
    gas_used = serializers.SerializerMethodField()
    fee = serializers.SerializerMethodField()
    origin = serializers.CharField()
    data_decoded = serializers.SerializerMethodField()
    confirmations_required = serializers.IntegerField()
    confirmations = serializers.SerializerMethodField()
    signatures = HexadecimalField()

    def get_block_number(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx.block_id

    def get_confirmations(self, obj: MultisigTransaction) -> Dict[str, Any]:
        """
        Filters confirmations queryset
        :param obj: MultisigConfirmation instance
        :return: Serialized queryset
        """
        return SafeMultisigConfirmationResponseSerializer(obj.confirmations, many=True).data

    def get_executor(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx._from

    def get_fee(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx:
            if obj.ethereum_tx.gas_used and obj.ethereum_tx.gas_price:
                return str(obj.ethereum_tx.gas_used * obj.ethereum_tx.gas_price)

    def get_eth_gas_price(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_price:
            return str(obj.ethereum_tx.gas_price)

    def get_gas_used(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_used:
            return obj.ethereum_tx.gas_used

    def get_is_successful(self, obj: MultisigTransaction) -> Optional[bool]:
        if obj.failed is None:
            return None
        else:
            return not obj.failed

    def get_data_decoded(self, obj: MultisigTransaction) -> Dict[str, Any]:
        return get_data_decoded_from_data(obj.data.tobytes() if obj.data else b'')
class TransferResponseSerializer(serializers.Serializer):
    type = serializers.SerializerMethodField()
    execution_date = serializers.DateTimeField()
    block_number = serializers.IntegerField()
    transaction_hash = Sha3HashField()
    to = EthereumAddressField()
    from_ = EthereumAddressField(source='_from')
    value = serializers.CharField()
    token_id = serializers.CharField()
    token_address = EthereumAddressField(allow_null=True, default=None)

    def get_fields(self):
        result = super().get_fields()
        # Rename `from_` to `from`
        from_ = result.pop('from_')
        result['from'] = from_
        return result

    def get_type(self, obj: Dict[str, Any]) -> str:
        if not obj.get('token_address'):
            return TransferType.ETHER_TRANSFER.name
        else:
            if obj.get('value') is not None:
                return TransferType.ERC20_TRANSFER.name
            elif obj.get('token_id') is not None:
                return TransferType.ERC721_TRANSFER.name

        return TransferType.UNKNOWN
class SafeCreationInfoResponseSerializer(serializers.Serializer):
    created = serializers.DateTimeField()
    creator = EthereumAddressField()
    factory_address = EthereumAddressField()
    master_copy = EthereumAddressField(allow_null=True)
    setup_data = HexadecimalField(allow_null=True)
    transaction_hash = Sha3HashField()
Ejemplo n.º 4
0
class TransferResponseSerializer(serializers.Serializer):
    type = serializers.SerializerMethodField()
    execution_date = serializers.DateTimeField()
    block_number = serializers.IntegerField(source="block")
    transaction_hash = Sha3HashField()
    to = EthereumAddressField()
    from_ = EthereumAddressField(source="_from", allow_zero_address=True)
    value = serializers.CharField(allow_null=True, source="_value")
    token_id = serializers.CharField(allow_null=True, source="_token_id")
    token_address = EthereumAddressField(allow_null=True, default=None)

    def get_fields(self):
        result = super().get_fields()
        # Rename `from_` to `from`
        from_ = result.pop("from_")
        result["from"] = from_
        return result

    def get_type(self, obj: TransferDict) -> str:
        if obj["token_address"] is None:
            return TransferType.ETHER_TRANSFER.name
        else:
            if obj["_value"] is not None:
                return TransferType.ERC20_TRANSFER.name
            elif obj["_token_id"] is not None:
                return TransferType.ERC721_TRANSFER.name
            else:
                return TransferType.UNKNOWN.name

    def validate(self, data):
        super().validate(data)
        if data["value"] is None and data["token_id"] is None:
            raise ValidationError("Both value and token_id cannot be null")
        return data
Ejemplo n.º 5
0
class SafeMultisigTxResponseSerializer(serializers.Serializer):
    to = EthereumAddressField(allow_null=True, allow_zero_address=True)
    ethereum_tx = EthereumTxSerializer()
    value = serializers.IntegerField(min_value=0)
    data = HexadecimalField()
    timestamp = serializers.DateTimeField(source='created')
    operation = serializers.SerializerMethodField()
    safe_tx_gas = serializers.IntegerField(min_value=0)
    data_gas = serializers.IntegerField(min_value=0)
    gas_price = serializers.IntegerField(min_value=0)
    gas_token = EthereumAddressField(allow_null=True, allow_zero_address=True)
    refund_receiver = EthereumAddressField(allow_null=True,
                                           allow_zero_address=True)
    nonce = serializers.IntegerField(min_value=0)
    safe_tx_hash = Sha3HashField()
    tx_hash = serializers.SerializerMethodField()
    transaction_hash = serializers.SerializerMethodField(
        method_name='get_tx_hash')  # Retro compatibility

    def get_operation(self, obj):
        """
        Filters confirmations queryset
        :param obj: MultisigConfirmation instance
        :return: serialized queryset
        """
        return SafeOperation(obj.operation).name

    def get_tx_hash(self, obj):
        tx_hash = obj.ethereum_tx.tx_hash
        if tx_hash and isinstance(tx_hash, bytes):
            return tx_hash.hex()
        return tx_hash
Ejemplo n.º 6
0
class SafeCreationResponseSerializer(serializers.Serializer):
    signature = SignatureResponseSerializer()
    tx = TransactionResponseSerializer()
    tx_hash = Sha3HashField()
    payment = serializers.CharField()
    payment_token = EthereumAddressField(allow_null=True, allow_zero_address=True)
    safe = EthereumAddressField()
    deployer = EthereumAddressField()
    funder = EthereumAddressField()
class SafeCreationInfoResponseSerializer(serializers.Serializer):
    created = serializers.DateTimeField()
    creator = EthereumAddressField()
    transaction_hash = Sha3HashField()
    factory_address = EthereumAddressField()
    master_copy = EthereumAddressField(allow_null=True)
    setup_data = HexadecimalField(allow_null=True)
    data_decoded = serializers.SerializerMethodField()

    def get_data_decoded(self, obj: SafeCreationInfo) -> Dict[str, Any]:
        return get_data_decoded_from_data(obj.setup_data or b'')
class IncomingTransactionResponseSerializer(serializers.Serializer):
    execution_date = serializers.DateTimeField()
    block_number = serializers.IntegerField()
    transaction_hash = Sha3HashField()
    to = EthereumAddressField()
    from_ = EthereumAddressField(source='_from')
    value = serializers.CharField()
    token_address = EthereumAddressField(allow_null=True, default=None)

    def get_fields(self):
        result = super().get_fields()
        # Rename `from_` to `from`
        from_ = result.pop('from_')
        result['from'] = from_
        return result
class SafeMultisigTransactionSerializer(SafeMultisigTxSerializerV1):
    contract_transaction_hash = Sha3HashField()
    sender = EthereumAddressField()
    # TODO Make signature mandatory
    signature = HexadecimalField(
        required=False, min_length=130)  # Signatures must be at least 65 bytes
    origin = serializers.CharField(max_length=100,
                                   allow_null=True,
                                   default=None)

    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)
        safe_tx = safe.build_multisig_tx(data['to'],
                                         data['value'],
                                         data['data'],
                                         data['operation'],
                                         data['safe_tx_gas'],
                                         data['base_gas'],
                                         data['gas_price'],
                                         data['gas_token'],
                                         data['refund_receiver'],
                                         safe_nonce=data['nonce'])
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data['contract_transaction_hash']:
            raise ValidationError(
                f'Contract-transaction-hash={contract_transaction_hash.hex()} '
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` or same `safeTxHash` for the same Safe.
        # We allow duplicated if existing tx is not executed
        multisig_transactions = MultisigTransaction.objects.filter(
            safe=safe.address, nonce=data['nonce']).executed()
        if multisig_transactions:
            for multisig_transaction in multisig_transactions:
                if multisig_transaction.safe_tx_hash == contract_transaction_hash.hex(
                ):
                    raise ValidationError(
                        f'Tx with safe-tx-hash={contract_transaction_hash.hex()} '
                        f'for safe={safe.address} was already executed in '
                        f'tx-hash={multisig_transaction.ethereum_tx_id}')

            raise ValidationError(
                f'Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} '
                f'already executed in tx-hash={multisig_transactions[0].ethereum_tx_id}'
            )

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier='pending')
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier='latest')

        data['safe_owners'] = safe_owners

        delegates = SafeContractDelegate.objects.get_delegates_for_safe(
            safe.address)
        allowed_senders = safe_owners + delegates
        if not data['sender'] in allowed_senders:
            raise ValidationError(
                f'Sender={data["sender"]} is not an owner or delegate. '
                f'Current owners={safe_owners}. Delegates={delegates}')

        signature_owners = []
        # TODO Make signature mandatory
        signature = data.get('signature', b'')
        parsed_signatures = SafeSignature.parse_signature(
            signature, contract_transaction_hash)
        data['parsed_signatures'] = parsed_signatures
        for safe_signature in parsed_signatures:
            owner = safe_signature.owner
            if not safe_signature.is_valid(ethereum_client, safe.address):
                raise ValidationError(
                    f'Signature={safe_signature.signature.hex()} for owner={owner} is not valid'
                )

            if owner in delegates and len(parsed_signatures) > 1:
                raise ValidationError(
                    f'Just one signature is expected if using delegates')
            if owner not in allowed_senders:
                raise ValidationError(
                    f'Signer={owner} is not an owner or delegate. '
                    f'Current owners={safe_owners}. Delegates={delegates}')
            if owner in signature_owners:
                raise ValidationError(
                    f'Signature for owner={owner} is duplicated')

            signature_owners.append(owner)

        # TODO Make signature mandatory. len(signature_owners) must be >= 1
        if signature_owners and data['sender'] not in signature_owners:
            raise ValidationError(
                f'Signature does not match sender={data["sender"]}. '
                f'Calculated owners={signature_owners}')

        return data

    def save(self, **kwargs):
        safe_tx_hash = self.validated_data['contract_transaction_hash']
        multisig_transaction, _ = MultisigTransaction.objects.get_or_create(
            safe_tx_hash=safe_tx_hash,
            defaults={
                'safe':
                self.validated_data['safe'],
                'to':
                self.validated_data['to'],
                'value':
                self.validated_data['value'],
                'data':
                self.validated_data['data']
                if self.validated_data['data'] else None,
                'operation':
                self.validated_data['operation'],
                'safe_tx_gas':
                self.validated_data['safe_tx_gas'],
                'base_gas':
                self.validated_data['base_gas'],
                'gas_price':
                self.validated_data['gas_price'],
                'gas_token':
                self.validated_data['gas_token'],
                'refund_receiver':
                self.validated_data['refund_receiver'],
                'nonce':
                self.validated_data['nonce'],
                'origin':
                self.validated_data['origin'],
            })

        for safe_signature in self.validated_data.get('parsed_signatures'):
            owner = safe_signature.owner
            if safe_signature.owner in self.validated_data['safe_owners']:
                multisig_confirmation, _ = MultisigConfirmation.objects.get_or_create(
                    multisig_transaction_hash=safe_tx_hash,
                    owner=owner,
                    defaults={
                        'multisig_transaction': multisig_transaction,
                        'signature': safe_signature.export_signature(),
                        'signature_type': safe_signature.signature_type.value,
                    })
        return multisig_transaction
Ejemplo n.º 10
0
class SafeFunding2ResponseSerializer(serializers.Serializer):
    block_number = serializers.CharField()
    tx_hash = Sha3HashField()
class SafeCreationInfoResponseSerializer(serializers.Serializer):
    created = serializers.DateTimeField()
    transaction_hash = Sha3HashField()
    creator = EthereumAddressField()
class SafeMultisigTransactionSerializer(SafeMultisigTxSerializerV1):
    contract_transaction_hash = Sha3HashField()
    sender = EthereumAddressField()
    signature = HexadecimalField(required=False)
    origin = serializers.CharField(max_length=100,
                                   allow_null=True,
                                   default=None)

    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data['safe'], ethereum_client)
        safe_tx = safe.build_multisig_tx(data['to'],
                                         data['value'],
                                         data['data'],
                                         data['operation'],
                                         data['safe_tx_gas'],
                                         data['base_gas'],
                                         data['gas_price'],
                                         data['gas_token'],
                                         data['refund_receiver'],
                                         safe_nonce=data['nonce'])
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data['contract_transaction_hash']:
            raise ValidationError(
                f'Contract-transaction-hash={contract_transaction_hash.hex()} '
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` for the same Safe.
        # We allow duplicated if existing tx is not executed
        try:
            multisig_transaction: MultisigTransaction = MultisigTransaction.objects.exclude(
                ethereum_tx=None).exclude(
                    safe_tx_hash=contract_transaction_hash).get(
                        safe=safe.address, nonce=data['nonce'])
            if multisig_transaction.safe_tx_hash != contract_transaction_hash:
                raise ValidationError(
                    f'Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} already executed in '
                    f'tx-hash={multisig_transaction.ethereum_tx_id}')
        except MultisigTransaction.DoesNotExist:
            pass

        # Check owners and old owners, owner might be removed but that tx can still be signed by that owner
        if not safe.retrieve_is_owner(data['sender']):
            try:
                # TODO Fix this, we can use SafeStatus now
                if not safe.retrieve_is_owner(
                        data['sender'],
                        block_identifier=max(
                            0, ethereum_client.current_block_number - 20)):
                    raise ValidationError('User is not an owner')
            except BadFunctionCallOutput:  # If it didn't exist 20 blocks ago
                raise ValidationError('User is not an owner')

        signature = data.get('signature')
        if signature is not None:
            #  TODO Support signatures with multiple owners
            if len(signature) != 65:
                raise ValidationError(
                    'Signatures with more than one owner still not supported')

            safe_signature = SafeSignature(signature,
                                           contract_transaction_hash)
            #  TODO Support contract signatures and approved hashes
            if safe_signature.signature_type == SafeSignatureType.CONTRACT_SIGNATURE:
                raise ValidationError('Contract signatures not supported')
            elif safe_signature.signature_type == SafeSignatureType.APPROVED_HASH:
                # Index it automatically later
                del data['signature']

            address = safe_signature.owner
            if address != data['sender']:
                raise ValidationError(
                    f'Signature does not match sender={data["sender"]}. Calculated owner={address}'
                )

        return data

    def save(self, **kwargs):
        multisig_transaction, _ = MultisigTransaction.objects.get_or_create(
            safe_tx_hash=self.validated_data['contract_transaction_hash'],
            defaults={
                'safe':
                self.validated_data['safe'],
                'to':
                self.validated_data['to'],
                'value':
                self.validated_data['value'],
                'data':
                self.validated_data['data']
                if self.validated_data['data'] else None,
                'operation':
                self.validated_data['operation'],
                'safe_tx_gas':
                self.validated_data['safe_tx_gas'],
                'base_gas':
                self.validated_data['base_gas'],
                'gas_price':
                self.validated_data['gas_price'],
                'gas_token':
                self.validated_data['gas_token'],
                'refund_receiver':
                self.validated_data['refund_receiver'],
                'nonce':
                self.validated_data['nonce'],
                'origin':
                self.validated_data['origin'],
            })

        if self.validated_data.get('signature'):
            MultisigConfirmation.objects.get_or_create(
                multisig_transaction_hash=multisig_transaction.safe_tx_hash,
                owner=self.validated_data['sender'],
                defaults={
                    'multisig_transaction': multisig_transaction,
                    'signature': self.validated_data.get('signature'),
                })
        return multisig_transaction
Ejemplo n.º 13
0
class SafeMultisigTransactionResponseSerializer(SafeMultisigTxSerializerV1):
    execution_date = serializers.DateTimeField()
    submission_date = serializers.DateTimeField(
        source="created")  # First seen by this service
    modified = serializers.DateTimeField()
    block_number = serializers.SerializerMethodField()
    transaction_hash = Sha3HashField(source="ethereum_tx_id")
    safe_tx_hash = Sha3HashField()
    executor = serializers.SerializerMethodField()
    value = serializers.CharField()
    is_executed = serializers.BooleanField(source="executed")
    is_successful = serializers.SerializerMethodField()
    gas_price = serializers.CharField()
    eth_gas_price = serializers.SerializerMethodField()
    max_fee_per_gas = serializers.SerializerMethodField()
    max_priority_fee_per_gas = serializers.SerializerMethodField()
    gas_used = serializers.SerializerMethodField()
    fee = serializers.SerializerMethodField()
    origin = serializers.CharField()
    data_decoded = serializers.SerializerMethodField()
    confirmations_required = serializers.IntegerField()
    confirmations = serializers.SerializerMethodField()
    trusted = serializers.BooleanField()
    signatures = HexadecimalField(allow_null=True, required=False)

    def get_block_number(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx.block_id

    @swagger_serializer_method(
        serializer_or_field=SafeMultisigConfirmationResponseSerializer)
    def get_confirmations(self, obj: MultisigTransaction) -> Dict[str, Any]:
        """
        Filters confirmations queryset
        :param obj: MultisigConfirmation instance
        :return: Serialized queryset
        """
        return SafeMultisigConfirmationResponseSerializer(obj.confirmations,
                                                          many=True).data

    def get_executor(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx_id:
            return obj.ethereum_tx._from

    def get_fee(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx:
            if obj.ethereum_tx.gas_used and obj.ethereum_tx.gas_price:
                return str(obj.ethereum_tx.gas_used *
                           obj.ethereum_tx.gas_price)

    def get_eth_gas_price(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_price:
            return str(obj.ethereum_tx.gas_price)

    def get_max_fee_per_gas(self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.max_fee_per_gas:
            return str(obj.ethereum_tx.max_fee_per_gas)

    def get_max_priority_fee_per_gas(
            self, obj: MultisigTransaction) -> Optional[str]:
        if obj.ethereum_tx and obj.ethereum_tx.max_priority_fee_per_gas:
            return str(obj.ethereum_tx.max_priority_fee_per_gas)

    def get_gas_used(self, obj: MultisigTransaction) -> Optional[int]:
        if obj.ethereum_tx and obj.ethereum_tx.gas_used:
            return obj.ethereum_tx.gas_used

    def get_is_successful(self, obj: MultisigTransaction) -> Optional[bool]:
        if obj.failed is None:
            return None
        else:
            return not obj.failed

    def get_data_decoded(self, obj: MultisigTransaction) -> Dict[str, Any]:
        # If delegate call contract must be whitelisted (security)
        if obj.data_should_be_decoded():
            return get_data_decoded_from_data(
                obj.data.tobytes() if obj.data else b"", address=obj.to)
Ejemplo n.º 14
0
class SafeMultisigTransactionSerializer(SafeMultisigTxSerializerV1):
    contract_transaction_hash = Sha3HashField()
    sender = EthereumAddressField()
    # TODO Make signature mandatory
    signature = HexadecimalField(
        allow_null=True, required=False,
        min_length=65)  # Signatures must be at least 65 bytes
    origin = serializers.CharField(max_length=200,
                                   allow_null=True,
                                   default=None)

    def validate(self, data):
        super().validate(data)

        ethereum_client = EthereumClientProvider()
        safe = Safe(data["safe"], ethereum_client)
        try:
            safe_version = safe.retrieve_version()
        except BadFunctionCallOutput as e:
            raise ValidationError(
                f"Could not get Safe version from blockchain, check contract exists on network "
                f"{ethereum_client.get_network().name}") from e
        except IOError:
            raise ValidationError(
                "Problem connecting to the ethereum node, please try again later"
            )

        safe_tx = safe.build_multisig_tx(
            data["to"],
            data["value"],
            data["data"],
            data["operation"],
            data["safe_tx_gas"],
            data["base_gas"],
            data["gas_price"],
            data["gas_token"],
            data["refund_receiver"],
            safe_nonce=data["nonce"],
            safe_version=safe_version,
        )
        contract_transaction_hash = safe_tx.safe_tx_hash

        # Check safe tx hash matches
        if contract_transaction_hash != data["contract_transaction_hash"]:
            raise ValidationError(
                f"Contract-transaction-hash={contract_transaction_hash.hex()} "
                f'does not match provided contract-tx-hash={data["contract_transaction_hash"].hex()}'
            )

        # Check there's not duplicated tx with same `nonce` or same `safeTxHash` for the same Safe.
        # We allow duplicated if existing tx is not executed
        multisig_transactions = MultisigTransaction.objects.filter(
            safe=safe.address, nonce=data["nonce"]).executed()
        if multisig_transactions:
            for multisig_transaction in multisig_transactions:
                if multisig_transaction.safe_tx_hash == contract_transaction_hash.hex(
                ):
                    raise ValidationError(
                        f"Tx with safe-tx-hash={contract_transaction_hash.hex()} "
                        f"for safe={safe.address} was already executed in "
                        f"tx-hash={multisig_transaction.ethereum_tx_id}")

            raise ValidationError(
                f"Tx with nonce={safe_tx.safe_nonce} for safe={safe.address} "
                f"already executed in tx-hash={multisig_transactions[0].ethereum_tx_id}"
            )

        # Check owners and pending owners
        try:
            safe_owners = safe.retrieve_owners(block_identifier="pending")
        except BadFunctionCallOutput:  # Error using pending block identifier
            safe_owners = safe.retrieve_owners(block_identifier="latest")
        except IOError:
            raise ValidationError(
                "Problem connecting to the ethereum node, please try again later"
            )

        data["safe_owners"] = safe_owners

        delegates = SafeContractDelegate.objects.get_delegates_for_safe_and_owners(
            safe.address, safe_owners)
        allowed_senders = set(safe_owners) | delegates
        if not data["sender"] in allowed_senders:
            raise ValidationError(
                f'Sender={data["sender"]} is not an owner or delegate. '
                f"Current owners={safe_owners}. Delegates={delegates}")

        signature_owners = []
        # TODO Make signature mandatory
        signature = data.get("signature", b"")
        parsed_signatures = SafeSignature.parse_signature(
            signature, contract_transaction_hash)
        data["parsed_signatures"] = parsed_signatures
        # If there's at least one signature, transaction is trusted (until signatures are mandatory)
        data["trusted"] = bool(parsed_signatures)
        for safe_signature in parsed_signatures:
            owner = safe_signature.owner
            if not safe_signature.is_valid(ethereum_client, safe.address):
                raise ValidationError(
                    f"Signature={safe_signature.signature.hex()} for owner={owner} is not valid"
                )

            if owner in delegates and len(parsed_signatures) > 1:
                raise ValidationError(
                    "Just one signature is expected if using delegates")
            if owner not in allowed_senders:
                raise ValidationError(
                    f"Signer={owner} is not an owner or delegate. "
                    f"Current owners={safe_owners}. Delegates={delegates}")
            if owner in signature_owners:
                raise ValidationError(
                    f"Signature for owner={owner} is duplicated")

            signature_owners.append(owner)

        # TODO Make signature mandatory. len(signature_owners) must be >= 1
        if signature_owners and data["sender"] not in signature_owners:
            raise ValidationError(
                f'Signature does not match sender={data["sender"]}. '
                f"Calculated owners={signature_owners}")

        return data

    def save(self, **kwargs):
        safe_tx_hash = self.validated_data["contract_transaction_hash"]
        origin = self.validated_data["origin"]
        trusted = self.validated_data["trusted"]
        if not trusted:
            # Check user permission
            if (self.context and (request := self.context.get("request"))
                    and (user := request.user)):
                trusted = user.has_perm("history.create_trusted")