def METHOD_NAME(contract, web3, contract_functions, func_name):
    # direct function calls
    if hasattr(contract, func_name):
        return getattr(contract, func_name)
    # contract functions
    def wrap(*args, **kwargs):
        args2 = list(args)
        tx_dict = None
        # retrieve tx dict from either args or kwargs
        if args and isinstance(args[-1], dict):
            tx_dict = args[-1] if args[-1].get("from") else None
            args2 = list(args[:-1])
        if "tx_dict" in kwargs:
            tx_dict = kwargs["tx_dict"] if kwargs["tx_dict"].get("from") else None
            del kwargs["tx_dict"]
        # use addresses instead of wallets when doing the call
        for arg in args2:
            if hasattr(arg, "address"):
                args2 = list(args2)
                args2[args2.index(arg)] = arg.address
        func = getattr(contract_functions, func_name)
        result = func(*args2, **kwargs)
        # view/pure functions don't need "from" key in tx_dict
        if not tx_dict and result.abi["stateMutability"] not in ["view", "pure"]:
            raise Exception("Needs tx_dict with 'from' key.")
        # if it's a view/pure function, just call it
        if result.abi["stateMutability"] in ["view", "pure"]:
            return result.call()
        else:
            # if it's a transaction, build and send it
            wallet = tx_dict["from"]
            tx_dict2 = tx_dict.copy()
            tx_dict2["nonce"] = web3.eth.get_transaction_count(wallet.address)
            tx_dict2["from"] = tx_dict["from"].address
            result = result.build_transaction(tx_dict2)
            # sign with wallet private key and send transaction
            signed_tx = web3.eth.account.sign_transaction(result, wallet._private_key)
            receipt = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
            return web3.eth.wait_for_transaction_receipt(receipt)
    return wrap