Exemplo n.º 1
0
 def _txdata_to_txobject(self, data: dict, descriptor: dict, metadata: dict = {}, compute_hash=True) -> TxObject:
     # initialize the right data size
     # this is PYTHON to POSTBODY
     schema = descriptor.get("schema", [])
     raw_data = [0] * (len(schema) + 1)  # the +1 is for the tag
     # set the tx tag first
     raw_data[0] = _int(data.get("tag"))
     # parse fields and encode them
     for label, fn in schema.items():
         if fn.field_type == _INT:
             raw_data[fn.index] = _int(data.get(label, 0))
         elif fn.field_type == _ID:
             raw_data[fn.index] = _id(data.get(label))
         elif fn.field_type == _ENC:
             raw_data[fn.index] = decode(data.get(label))
         elif fn.field_type == _OTTL_TYPE:
             raw_data[fn.index] = _int(idf.ORACLE_TTL_TYPES.get(data.get(label)))
         elif fn.field_type == _SG:
             # signatures are always a list
             raw_data[fn.index] = [decode(sg) for sg in data.get(label, [])]
         elif fn.field_type == _VM_ABI:
             # vm/abi are encoded in the same 32bit length block
             raw_data[fn.index] = _int(data.get("vm_version")) + _int(data.get("abi_version"), 2)
         elif fn.field_type == _BIN:
             # this are binary string #TODO: may be byte array
             raw_data[fn.index] = _binary(data.get(label))
         elif fn.field_type == _PTR:
             # this are name pointers
             raw_data[fn.index] = [[_binary(p.get("key")), _id(p.get("id"))] for p in data.get(label, [])]
         elif fn.field_type == _TX:
             # this can be raw or tx object
             tx = data.get(label).tx if hasattr(data.get(label), "tx") else data.get(label)
             raw_data[fn.index] = decode(tx)
     # encode the transaction in rlp
     rlp_tx = rlp.encode(raw_data)
     # encode the tx in base64
     rlp_b64_tx = encode(idf.TRANSACTION, rlp_tx)
     # copy the data before modifying
     tx_data = copy.deepcopy(data)
     # build the tx object
     txo = TxObject(
         data=namedtupled.map(tx_data, _nt_name="TxData"),
         tx=rlp_b64_tx,
     )
     # compute the tx hash
     if compute_hash:
         txo.hash = hash_encode(idf.TRANSACTION_HASH, rlp_tx)
     # copy the metadata if exists or initialize it if None
     tx_meta = copy.deepcopy(metadata) if metadata is not None else {}
     # compute the minimum fee
     if descriptor.get("fee") is not None:
         tx_meta["min_fee"] = self.compute_min_fee(data, descriptor, raw_data)
     # only set the metadata if it is not empty
     txo.set_metadata(tx_meta)
     return txo
Exemplo n.º 2
0
 def oracle_fee(tx_raw, fee_idx, relative_ttl):
     # estimate oracle fees
     tx_copy = tx_raw  # create a copy of the input
     tx_copy[fee_idx] = _int(0, 8)  # replace fee with a byte array of lenght 8
     fee = (defaults.BASE_GAS + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE)
     fee += math.ceil(32000 * relative_ttl / math.floor(60 * 24 * 365 / defaults.KEY_BLOCK_INTERVAL))
     return fee * defaults.GAS_PRICE
 def contract_fee(tx_raw, fee_idx, gas, base_gas_multiplier=1):
     # estimate the contract creation fee
     tx_copy = tx_raw  # create a copy of the input
     tx_copy[fee_idx] = _int(0,
                             8)  # replace fee with a byte array of lenght 8
     return (defaults.BASE_GAS * base_gas_multiplier + gas + len(
         rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE
 def std_fee(tx_raw, fee_idx, base_gas_multiplier=1):
     # calculates the standard minimum transaction fee
     tx_copy = tx_raw  # create a copy of the input
     tx_copy[fee_idx] = _int(0,
                             8)  # replace fee with a byte array of lenght 8
     return (defaults.BASE_GAS * base_gas_multiplier + len(
         rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE
Exemplo n.º 5
0
    def compute_min_fee(self, tx_data: dict, tx_descriptor: dict,
                        tx_raw: list) -> int:
        """
        Compute the minimum fee for a transaction

        Args:
            tx_data (dict): the transaction data
            tx_descriptor (dict): the descriptor of the transaction fields
            tx_raw: the unencoded rlp data of the transaction
        Returns:
            the minimum fee for the transaction
        """
        # if the fee is none means it is not necessary to compute the fee
        if tx_descriptor.get("fee") is None:
            return 0
        # extract the parameters from the descriptor
        fee_field_index = tx_descriptor.get("schema").get("fee").index
        fee_type = tx_descriptor.get("fee").fee_type
        base_gas_multiplier = tx_descriptor.get("fee").base_gas_multiplier
        # for property based multiplier
        if isinstance(base_gas_multiplier, tuple):
            # check CONTRACT_CALL definition for an example
            field_modifier_value = tx_data.get(base_gas_multiplier[0])
            base_gas_multiplier = base_gas_multiplier[1].get(
                field_modifier_value)
        # if the fee is ttl based compute the ttl component
        ttl_component = 0
        if fee_type is TTL_BASED:
            ttl = tx_data.get(tx_descriptor.get("fee").ttl_field)
            ttl_component += (math.ceil(
                32000 * ttl /
                math.floor(60 * 24 * 365 / self.key_block_interval)))
        # if the fee is for a ga meta compute the enclosed tx size based fee
        ga_surplus = 0
        if fee_type == GA_META:
            tx_size = len(tx_raw[tx_descriptor.get("tx").index])
            ga_surplus = (self.base_gas * base_gas_multiplier +
                          tx_size * self.gas_per_byte)
        # begin calculation
        current_fee, min_fee = -1, defaults.FEE
        while current_fee != min_fee:
            # save the min fee into current fee
            current_fee = min_fee
            # update the fee value
            tx_raw[fee_field_index] = _int(current_fee)
            # first calculate the size part
            tx_size = len(rlp.encode(tx_raw))
            min_fee = self.base_gas * base_gas_multiplier + tx_size * self.gas_per_byte
            # add the ttl_component
            min_fee += ttl_component
            # remove the ga_surplus
            min_fee -= ga_surplus
            # finally multiply for gas the gas price
            min_fee *= self.gas_price

        return min_fee
Exemplo n.º 6
0
 def build_tx_object(tx_data, tx_raw, fee_idx, min_fee):
     if tx_data.get("fee") < min_fee:
         tx_native[fee_idx] = _int(min_fee)
         tx_data["fee"] = min_fee
     tx_encoded = encode_rlp(idf.TRANSACTION, tx_native)
     tx = dict(
         data=tx_data,
         tx=tx_encoded,
         hash=TxBuilder.compute_tx_hash(tx_encoded),
     )
     return namedtupled.map(tx, _nt_name="TxObject")
 def build_tx_object(tx_data, tx_raw, fee_idx, min_fee):
     # if fee is not set use the min fee
     if tx_data.get("fee") <= 0:
         tx_data["fee"] = min_fee
     # if it is set check that is greater then the minimum fee
     elif tx_data.get("fee") < min_fee:
         raise TransactionFeeTooLow(
             f'Minimum transaction fee is {min_fee}, provided fee is {tx_data.get("fee")}'
         )
     tx_native[fee_idx] = _int(tx_data.get("fee"))
     tx_encoded = encode_rlp(idf.TRANSACTION, tx_native)
     tx = dict(
         data=tx_data,
         tx=tx_encoded,
         hash=TxBuilder.compute_tx_hash(tx_encoded),
     )
     return namedtupled.map(tx, _nt_name="TxObject")
Exemplo n.º 8
0
    def compute_min_fee(self, tx_data: dict, tx_descriptor: dict,
                        tx_raw: list) -> int:
        # if the fee is none means it is not necessary to compute the fee
        if tx_descriptor.get("fee") is None:
            return 0
        # extract the parameters from the descriptor
        fee_field_index = tx_descriptor.get("schema").get("fee").index
        base_gas_multiplier = tx_descriptor.get("fee").base_gas_multiplier
        fee_type = tx_descriptor.get("fee").fee_type
        # if the fee is ttl based compute the ttl component
        ttl_component = 0
        if fee_type is TTL_BASED:
            ttl = tx_data.get(tx_descriptor.get("fee").ttl_field)
            ttl_component += (math.ceil(
                32000 * ttl /
                math.floor(60 * 24 * 365 / self.key_block_interval)))
        # if the fee is for a ga meta compute the enclosed tx size based fee
        ga_surplus = 0
        if fee_type == GA_META:
            tx_size = len(tx_raw[tx_descriptor.get("tx").index])
            ga_surplus = (self.base_gas * base_gas_multiplier +
                          tx_size * self.gas_per_byte)
        # begin calculation
        current_fee, min_fee = -1, defaults.FEE
        while current_fee != min_fee:
            # save the min fee into current fee
            current_fee = min_fee
            # update the fee value
            tx_raw[fee_field_index] = _int(current_fee)
            # first calculate the size part
            tx_size = len(rlp.encode(tx_raw))
            min_fee = self.base_gas * base_gas_multiplier + tx_size * self.gas_per_byte
            # add the ttl_component
            min_fee += ttl_component
            # remove the ga_surplus
            min_fee -= ga_surplus
            # finally multiply for gas the gas price
            min_fee *= self.gas_price

        return min_fee
Exemplo n.º 9
0
def _tx_native(op, **kwargs):

    def std_fee(tx_raw, fee_idx, base_gas_multiplier=1):
        # calculates the standard minimum transaction fee
        tx_copy = tx_raw  # create a copy of the input
        tx_copy[fee_idx] = _int(0, 8)  # replace fee with a byte array of lenght 8
        return (defaults.BASE_GAS * base_gas_multiplier + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE

    def contract_fee(tx_raw, fee_idx, gas, base_gas_multiplier=1):
        # estimate the contract creation fee
        tx_copy = tx_raw  # create a copy of the input
        tx_copy[fee_idx] = _int(0, 8)  # replace fee with a byte array of lenght 8
        return (defaults.BASE_GAS * base_gas_multiplier + gas + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE

    def oracle_fee(tx_raw, fee_idx, relative_ttl):
        # estimate oracle fees
        tx_copy = tx_raw  # create a copy of the input
        tx_copy[fee_idx] = _int(0, 8)  # replace fee with a byte array of lenght 8
        fee = (defaults.BASE_GAS + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE)
        fee += math.ceil(32000 * relative_ttl / math.floor(60 * 24 * 365 / defaults.KEY_BLOCK_INTERVAL))
        return fee * defaults.GAS_PRICE

    def build_tx_object(tx_data, tx_raw, fee_idx, min_fee):
        if tx_data.get("fee") < min_fee:
            tx_native[fee_idx] = _int(min_fee)
            tx_data["fee"] = min_fee
        tx_encoded = encode_rlp(idf.TRANSACTION, tx_native)
        tx = dict(
            data=tx_data,
            tx=tx_encoded,
            hash=TxBuilder.compute_tx_hash(tx_encoded),
        )
        return namedtupled.map(tx, _nt_name="TxObject")

    # prepare tag and version
    if op == PACK_TX:
        tag = kwargs.get("tag", 0)
        vsn = kwargs.get("vsn", 1)
        tx_data = kwargs
    elif op == UNPACK_TX:
        tx_native = decode_rlp(kwargs.get("tx", []))
        tag = _int_decode(tx_native[0])
    else:
        raise Exception("Invalid operation")

    # check the tags
    if tag == idf.OBJECT_TAG_SPEND_TRANSACTION:
        tx_field_fee_index = 5
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("sender_id")),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("recipient_id")),
                _int(kwargs.get("amount")),
                _int(kwargs.get("fee")),  # index 5
                _int(kwargs.get("ttl")),
                _int(kwargs.get("nonce")),
                _binary(kwargs.get("payload"))
            ]
            min_fee = std_fee(tx_native, tx_field_fee_index)
        elif op == UNPACK_TX:  # unpack transaction
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                sender_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                recipient_id=encode(idf.ACCOUNT_ID, tx_native[3][1:]),
                amount=_int_decode(tx_native[4]),
                fee=_int_decode(tx_native[5]),
                ttl=_int_decode(tx_native[6]),
                nonce=_int_decode(tx_native[7]),
                payload=_binary_decode(tx_native[8]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)
    elif tag == idf.OBJECT_TAG_NAME_SERVICE_PRECLAIM_TRANSACTION:
        tx_field_fee_index = 5
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")),
                _int(kwargs.get("nonce")),
                _id(idf.ID_TAG_COMMITMENT, kwargs.get("commitment_id")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl"))
            ]
            min_fee = std_fee(tx_native, tx_field_fee_index)
        elif op == UNPACK_TX:  # unpack transaction
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                commitment_id=encode(idf.COMMITMENT, tx_native[4][1:]),
                fee=_int_decode(tx_native[5]),
                ttl=_int_decode(tx_native[6]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_NAME_SERVICE_CLAIM_TRANSACTION:
        tx_field_fee_index = 6
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")),
                _int(kwargs.get("nonce")),
                decode(kwargs.get("name")),
                _binary(kwargs.get("name_salt")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl"))
            ]
            min_fee = std_fee(tx_native, tx_field_fee_index)
        elif op == UNPACK_TX:  # unpack transaction
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                name=_binary_decode(tx_native[4], str),
                name_salt=_binary_decode(tx_native[5], int),
                fee=_int_decode(tx_native[6]),
                ttl=_int_decode(tx_native[7]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_NAME_SERVICE_UPDATE_TRANSACTION:
        tx_field_fee_index = 8
        if op == PACK_TX:  # pack transaction
            # first asseble the pointers
            def pointer_tag(pointer):
                return {
                    "account_pubkey": idf.ID_TAG_ACCOUNT,
                    "oracle_pubkey": idf.ID_TAG_ORACLE,
                    "contract_pubkey": idf.ID_TAG_CONTRACT,
                    "channel_pubkey": idf.ID_TAG_CHANNEL
                }.get(pointer.get("key"))
            ptrs = [[_binary(p.get("key")), _id(pointer_tag(p), p.get("id"))] for p in kwargs.get("pointers", [])]
            # then build the transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")),
                _int(kwargs.get("nonce")),
                _id(idf.ID_TAG_NAME, kwargs.get("name_id")),
                _int(kwargs.get("name_ttl")),
                ptrs,
                _int(kwargs.get("client_ttl")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl"))
            ]
            min_fee = std_fee(tx_native, tx_field_fee_index)
        elif op == UNPACK_TX:  # unpack transaction
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                name=encode(idf.NAME, tx_native[4][1:]),
                name_ttl=_int_decode(tx_native[5]),
                pointers=[],  # TODO: decode pointers
                client_ttl=_int_decode(tx_native[7]),
                fee=_int_decode(tx_native[8]),
                ttl=_int_decode(tx_native[9]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_NAME_SERVICE_TRANSFER_TRANSACTION:
        tx_field_fee_index = 6
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")),
                _int(kwargs.get("nonce")),
                _id(idf.ID_TAG_NAME, kwargs.get("name_id")),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("recipient_id")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
            ]
            min_fee = std_fee(tx_native, tx_field_fee_index)
        elif op == UNPACK_TX:  # unpack transaction
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                name=encode(idf.NAME, tx_native[4][1:]),
                recipient_id=encode(idf.ACCOUNT_ID, tx_native[5][1:]),
                fee=_int_decode(tx_native[6]),
                ttl=_int_decode(tx_native[7]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_NAME_SERVICE_REVOKE_TRANSACTION:
        tx_field_fee_index = 5
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")),
                _int(kwargs.get("nonce")),
                _id(idf.ID_TAG_NAME, kwargs.get("name_id")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
            ]
            min_fee = std_fee(tx_native, tx_field_fee_index)
        elif op == UNPACK_TX:  # unpack transaction
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                name=encode(idf.NAME, tx_native[4][1:]),
                fee=_int_decode(tx_native[5]),
                ttl=_int_decode(tx_native[6]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_CONTRACT_CREATE_TRANSACTION:
        tx_field_fee_index = 6
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("owner_id")),
                _int(kwargs.get("nonce")),
                _binary(decode(kwargs.get("code"))),
                _int(kwargs.get("vm_version")) + _int(kwargs.get("abi_version"), 2),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
                _int(kwargs.get("deposit")),
                _int(kwargs.get("amount")),
                _int(kwargs.get("gas")),
                _int(kwargs.get("gas_price")),
                _binary(decode(kwargs.get("call_data"))),
            ]
            # TODO: verify the fee caluclation for the contract
            min_fee = contract_fee(tx_native, tx_field_fee_index, kwargs.get("gas"),  base_gas_multiplier=5)
        elif op == UNPACK_TX:  # unpack transaction
            vml = len(tx_native[5])  # this is used to extract the abi and vm version from the 5th field
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                owner_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                code=encode(idf.BYTECODE, tx_native[4][1:]),
                vm_version=_int_decode(tx_native[5][0:vml - 2]),
                abi_version=_int_decode(tx_native[5][vml - 2:]),
                fee=_int_decode(tx_native[6]),
                ttl=_int_decode(tx_native[7]),
                deposit=_int_decode(tx_native[8]),
                amount=_int_decode(tx_native[9]),
                gas=_int_decode(tx_native[10]),
                gas_price=_int_decode(tx_native[11]),
                call_data=encode(idf.BYTECODE, tx_native[12][1:]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_CONTRACT_CALL_TRANSACTION:
        tx_field_fee_index = 6
        if op == PACK_TX:  # pack transaction
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("caller_id")),
                _int(kwargs.get("nonce")),
                _id(idf.ID_TAG_CONTRACT, kwargs.get("contract_id")),
                _int(kwargs.get("abi_version")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
                _int(kwargs.get("amount")),
                _int(kwargs.get("gas")),
                _int(kwargs.get("gas_price")),
                _binary(decode(kwargs.get("call_data"))),
            ]
            min_fee = contract_fee(tx_native, tx_field_fee_index, kwargs.get("gas"),  base_gas_multiplier=30)
        elif op == UNPACK_TX:  # unpack transaction
            vml = len(tx_native[5])  # this is used to extract the abi and vm version from the 5th field
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                caller_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                contract_id=encode(idf.CONTRACT_ID, tx_native[4][1:]),
                abi_version=_int_decode(tx_native[5]),
                fee=_int_decode(tx_native[6]),
                ttl=_int_decode(tx_native[7]),
                amount=_int_decode(tx_native[8]),
                gas=_int_decode(tx_native[9]),
                gas_price=_int_decode(tx_native[10]),
                call_data=encode(idf.BYTECODE, tx_native[11][1:]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_CHANNEL_CREATE_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("initiator")),
            _int(kwargs.get("initiator_amount")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("responder")),
            _int(kwargs.get("responder_amount")),
            _int(kwargs.get("channel_reserve")),
            _int(kwargs.get("lock_period")),
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            # _[id(delegate_ids)], TODO: handle delegate ids
            _binary(kwargs.get("state_hash")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 9
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_DEPOSIT_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _int(kwargs.get("amount")),
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _binary(kwargs.get("state_hash")),
            _int(kwargs.get("round")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 6
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_WITHDRAW_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("to_id")),
            _int(kwargs.get("amount")),
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _binary(kwargs.get("state_hash")),
            _int(kwargs.get("round")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 6
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_CLOSE_MUTUAL_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _int(kwargs.get("initiator_amount_final")),
            _int(kwargs.get("responder_amount_final")),
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 7
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_CLOSE_SOLO_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _binary(kwargs.get("payload")),
            # _poi(kwargs.get("poi")), TODO: implement support for _poi
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 7
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_SLASH_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _binary(kwargs.get("payload")),
            # _poi(kwargs.get("poi")), TODO: implement support for _poi
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 7
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_SETTLE_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _int(kwargs.get("initiator_amount_final")),
            _int(kwargs.get("responder_amount_final")),
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 7
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_SNAPSHOT_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _binary(kwargs.get("payload")),
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 6
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_CHANNEL_FORCE_PROGRESS_TRANSACTION:
        tx_native = [
            _int(tag),
            _int(vsn),
            _id(idf.ID_TAG_CHANNEL, kwargs.get("channel_id")),
            _id(idf.ID_TAG_ACCOUNT, kwargs.get("from_id")),
            _binary(kwargs.get("payload")),
            _int(kwargs.get("round")),
            _binary(kwargs.get("update")),
            _binary(kwargs.get("state_hash")),
            # _trees(kwargs.get("offchain_trees")), TODO: implement support for _trees
            _int(kwargs.get("ttl")),
            _int(kwargs.get("fee")),
            _int(kwargs.get("nonce")),
        ]
        tx_field_fee_index = 9
        min_fee = std_fee(tx_native, tx_field_fee_index)
    elif tag == idf.OBJECT_TAG_ORACLE_REGISTER_TRANSACTION:
        tx_field_fee_index = 9
        if op == PACK_TX:  # pack transaction
            oracle_ttl = kwargs.get("oracle_ttl", {})
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")),
                _int(kwargs.get("nonce")),
                _binary(kwargs.get("query_format")),
                _binary(kwargs.get("response_format")),
                _int(kwargs.get("query_fee")),
                _int(0 if oracle_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1),
                _int(oracle_ttl.get("value")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
                _int(kwargs.get("vm_version")),
            ]
            min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value"))
        elif op == UNPACK_TX:  # unpack transaction
            vml = len(tx_native[5])  # this is used to extract the abi and vm version from the 5th field
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                query_format=_binary_decode(tx_native[4]),
                response_format=_binary_decode(tx_native[5]),
                query_fee=_int_decode(tx_native[6]),
                oracle_ttl=dict(
                    type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else idf.ORACLE_TTL_TYPE_BLOCK,
                    value=_int_decode(tx_native[8]),
                ),
                fee=_int_decode(tx_native[9]),
                ttl=_int_decode(tx_native[10]),
                vm_version=_int_decode(tx_native[11]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_ORACLE_QUERY_TRANSACTION:
        tx_field_fee_index = 11
        if op == PACK_TX:  # pack transaction
            query_ttl = kwargs.get("query_ttl", {})
            response_ttl = kwargs.get("response_ttl", {})
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ACCOUNT, kwargs.get("sender_id")),
                _int(kwargs.get("nonce")),
                _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")),
                _binary(kwargs.get("query")),
                _int(kwargs.get("query_fee")),
                _int(0 if query_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1),
                _int(query_ttl.get("value")),
                _int(0 if response_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1),
                _int(response_ttl.get("value")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
            ]
            min_fee = oracle_fee(tx_native, tx_field_fee_index, query_ttl.get("value"))
        elif op == UNPACK_TX:  # unpack transaction
            vml = len(tx_native[5])  # this is used to extract the abi and vm version from the 5th field
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                sender_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                oracle_id=encode(idf.ORACLE_ID, tx_native[4][1:]),
                query=_binary_decode(tx_native[5]),
                query_fee=_int_decode(tx_native[6]),
                query_ttl=dict(
                    type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else idf.ORACLE_TTL_TYPE_BLOCK,
                    value=_int_decode(tx_native[8]),
                ),
                response_ttl=dict(
                    type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[9]) else idf.ORACLE_TTL_TYPE_BLOCK,
                    value=_int_decode(tx_native[10]),
                ),
                fee=_int_decode(tx_native[11]),
                ttl=_int_decode(tx_native[12]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_ORACLE_RESPONSE_TRANSACTION:
        tx_field_fee_index = 8
        if op == PACK_TX:  # pack transaction
            response_ttl = kwargs.get("response_ttl", {})
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")),
                _int(kwargs.get("nonce")),
                decode(kwargs.get("query_id")),
                _binary(kwargs.get("response")),
                _int(0 if response_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1),
                _int(response_ttl.get("value")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
            ]
            min_fee = oracle_fee(tx_native, tx_field_fee_index, response_ttl.get("value"))
        elif op == UNPACK_TX:  # unpack transaction
            vml = len(tx_native[5])  # this is used to extract the abi and vm version from the 5th field
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                oracle_id=encode(idf.ORACLE_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                query_id=encode(idf.ORACLE_QUERY_ID, tx_native[4][1:]),
                response=_binary(tx_native[5]),
                response_ttl=dict(
                    type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[6]) else idf.ORACLE_TTL_TYPE_BLOCK,
                    value=_int_decode(tx_native[7]),
                ),
                fee=_int_decode(tx_native[8]),
                ttl=_int_decode(tx_native[9]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)

    elif tag == idf.OBJECT_TAG_ORACLE_EXTEND_TRANSACTION:
        tx_field_fee_index = 6
        if op == PACK_TX:  # pack transaction
            oracle_ttl = kwargs.get("oracle_ttl", {})
            tx_native = [
                _int(tag),
                _int(vsn),
                _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")),
                _int(kwargs.get("nonce")),
                _int(0 if oracle_ttl.get("type", {}) == idf.ORACLE_TTL_TYPE_DELTA else 1),
                _int(oracle_ttl.get("value")),
                _int(kwargs.get("fee")),
                _int(kwargs.get("ttl")),
            ]
            min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value"))
        elif op == UNPACK_TX:  # unpack transaction
            vml = len(tx_native[5])  # this is used to extract the abi and vm version from the 5th field
            tx_data = dict(
                tag=tag,
                vsn=_int_decode(tx_native[1]),
                oracle_id=encode(idf.ORACLE_ID, tx_native[2][1:]),
                nonce=_int_decode(tx_native[3]),
                oracle_ttl=dict(
                    type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[4]) else idf.ORACLE_TTL_TYPE_BLOCK,
                    value=_int_decode(tx_native[5]),
                ),
                fee=_int_decode(tx_native[6]),
                ttl=_int_decode(tx_native[7]),
            )
            min_fee = tx_data.get("fee")
        else:
            raise Exception("Invalid operation")
        return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee)
    else:
        raise UnsupportedTransactionType(f"Unusupported transaction tag {tag}")