import os
import json
import hexbytes
import logging as log
from eth_abi import decode
from eth_abi.codec import ABICodec
from web3._utils.events import get_event_data

from rubi.contracts.helper import networks


# DEPRECATED
# TODO: this is the old implementation for the RubiconMarketAidFactory and RubiconMarketAid. It needs to be updated in
#  line with all the other contracts in this repo. Usage is not recommended in it's current state and it is not included
#  in any of the __init__.py files.
class FactoryAid:
    """this class represents the MarketAidFactory.sol contract and has read functionality for the contract

    :param w3: Web3 instance
    :type w3: Web3
    :param contract: an optional contract instance, if not provided, the contract will be instantiated using the address and abi from the networks.py file given the chain id of the w3 instance
    :type contract: Web3 object, optional
    """

    def __init__(self, w3, contract=None):
        """constructor method"""
        raise DeprecationWarning()

        chain = w3.eth.chain_id

        if contract:
            self.contract = contract
            self.address = self.contract.address
        else:
            # TODO: add error handling for unsupported chains
            network = networks[chain]()
            self.contract = w3.eth.contract(
                address=network.factory, abi=network.factory_abi
            )
            self.address = network.factory

        # set the class variables
        self.chain = chain
        self.w3 = w3

    ######################################################################
    # read calls
    ######################################################################

    # admin()
    def admin(self):
        """returns the admin address of the contract

        :return: the admin address
        :rtype: str
        """

        try:
            admin = self.contract.functions.admin().call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return admin

    # getUserMarketAids(user (address))
    def get_user_market_aids(self, user):
        """returns the market aid addresses for a given user

        :return: an array of market aid addresses
        :rtype: list
        """

        try:
            aids = self.contract.functions.getUserMarketAids(user).call()
        except ValueError:
            aids = self.contract.functions.getUserMarketAids(
                self.w3.to_checksum_address(user)
            ).call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return aids

    # rubiconMarket()
    def rubicon_market(self):
        """returns the address of the RubiconMarket contract

        :return: rubicon market address
        :rtype: str
        """

        try:
            rubicon_market = self.contract.functions.rubiconMarket().call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return rubicon_market


class FactoryAidSigner(FactoryAid):
    """this class represents the MarketAidFactory.sol contract and has read and write functionality for the contract. this class inherits from the FactoryAid class

    :param w3: a web3 instance
    :type w3: Web3
    :param wallet: the signers wallet address
    :type wallet: str
    :param key: the signers private key
    :type key: str
    :param contract: an optional parameter that allows you to pass in a contract instance, if none the contract will be instantiated from the rolodex.py file
    :type contract_address: str, optional
    """

    def __init__(self, w3, wallet, key, contract=None):
        super().__init__(w3, contract)
        self.wallet = wallet
        self.key = key

    ######################################################################
    # write calls
    ######################################################################

    # createMarketAidInstance()
    def create_market_aid_instance(self, nonce=None, gas=3000000, gas_price=None):
        """this function creates a new market aid instance

        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the create market aid instance transaction, None if there is an error
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            create = (
                self.contract.functions.createMarketAidInstance().build_transaction(txn)
            )
            create = self.w3.eth.account.sign_transaction(create, self.key)
            self.w3.eth.send_raw_transaction(create.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(create.hash)["status"] == 0:
                    log.error(
                        f"create_market_aid_instance transaction failed: {create.hash.hex()}"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return create


class MarketAid:
    # init function
    # TODO: possibly allow a chain variable to be passed in
    def __init__(self, w3, address, contract=None):
        raise DeprecationWarning()

        # load in the aid abi
        path = (
            f"{os.path.dirname(os.path.realpath(__file__))}/helper/abis/MarketAid.json"
        )
        with open(path) as f:
            aid_abi = json.load(f)
        f.close()

        # create contract instance or set based upon initiliazation
        if contract:
            self.contract = contract
            self.address = self.contract.address
            chain = w3.eth.chain_id
        else:
            chain = w3.eth.chain_id
            # network = networks[chain]()
            self.contract = w3.eth.contract(address=address, abi=aid_abi)
            self.address = address

        # set the class variables
        self.chain = chain
        self.w3 = w3
        self.log_strategist_trade_abi = (
            self.contract.events.LogStrategistTrade()._get_event_abi()
        )
        self.log_scrubbed_strat_trade_abi = (
            self.contract.events.LogScrubbedStratTrade()._get_event_abi()
        )
        self.log_batch_market_making_trades_abi = (
            self.contract.events.LogBatchMarketMakingTrades()._get_event_abi()
        )
        self.log_requote_abi = self.contract.events.LogRequote()._get_event_abi()
        self.log_batch_requote_offers_abi = (
            self.contract.events.LogBatchRequoteOffers()._get_event_abi()
        )
        self.codec: ABICodec = w3.codec

    ######################################################################
    # read calls
    ######################################################################

    # RubiconMarketAddress()
    def rubicon_market_address(self):
        """this function returns the address of the RubiconMarket contract

        :return: the address of the RubiconMarket contract
        :rtype: str
        """

        try:
            rubicon_market_address = (
                self.contract.functions.RubiconMarketAddress().call()
            )
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return rubicon_market_address

    # admin()
    def admin(self):
        """this function returns the address of the admin

        :return: the address of the admin
        :rtype: str
        """

        try:
            admin = self.contract.functions.admin().call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return admin

    # approvedStrategists()
    def approved_strategists(self, address):
        """this function returns the list of approved strategists

        :return: the list of approved strategists
        :rtype: list
        """

        try:
            approved_strategists = self.contract.functions.approvedStrategists(
                address
            ).call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return approved_strategists

    # getOutstandingStrategistTrades(asset (address), quote (address), strategist (address))
    def get_outstanding_strategist_trades(self, asset, quote, strategist):
        """this function returns the list of outstanding trades for a strategist

        :param asset: the address of the asset
        :type asset: str
        :param quote: the address of the quote
        :type quote: str
        :param strategist: the address of the strategist
        :type strategist: str
        :return: the list of outstanding trades for a strategist
        :rtype: list
        """

        try:
            outstanding_strategist_trades = (
                self.contract.functions.getOutstandingStrategistTrades(
                    asset, quote, strategist
                ).call()
            )
        except ValueError:
            outstanding_strategist_trades = (
                self.contract.functions.getOutstandingStrategistTrades(
                    self.w3.to_checksum_address(asset),
                    self.w3.to_checksum_address(quote),
                    self.w3.to_checksum_address(strategist),
                ).call()
            )
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return outstanding_strategist_trades

    # getStrategistTotalLiquidity(asset (address), quote (address), strategist (address))
    def get_strategist_total_liquidity(self, asset, quote, strategist):
        """this function returns the total liquidity for a strategist

        :param asset: the address of the asset
        :type asset: str
        :param quote: the address of the quote
        :type quote: str
        :param strategist: the address of the strategist
        :type strategist: str
        :return: the total liquidity for a strategist
        :rtype: int
        """

        try:
            strategist_total_liquidity = (
                self.contract.functions.getStrategistTotalLiquidity(
                    asset, quote, strategist
                ).call()
            )
        except ValueError:
            strategist_total_liquidity = (
                self.contract.functions.getStrategistTotalLiquidity(
                    self.w3.to_checksum_address(asset),
                    self.w3.to_checksum_address(quote),
                    self.w3.to_checksum_address(strategist),
                ).call()
            )
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return strategist_total_liquidity

    # isApprovedStrategist(strategist (address))
    def is_approved_strategist(self, strategist):
        """this function returns whether or not a strategist is approved

        :param strategist: the address of the strategist
        :type strategist: str
        :return: whether or not a strategist is approved
        :rtype: bool
        """

        try:
            is_approved_strategist = self.contract.functions.isApprovedStrategist(
                strategist
            ).call()
        except ValueError:
            is_approved_strategist = self.contract.functions.isApprovedStrategist(
                self.w3.to_checksum_address(strategist)
            ).call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return is_approved_strategist

    def get_strategist_trade(self, trade_id):
        """this function returns a strategist trade

        :param trade_id: the id of the trade
        :type trade_id: int
        :return: a an array containing the relevant information for a pair of offers: [ask_id, ask_pay_amt, ask_asset, bid_id, bid_pay_amt, bid_asset, strategist, timestamp]
        :rtype: dict
        """

        # check that the trade id is an integer
        if not isinstance(trade_id, int):
            log.error("trade id is not an integer")
            return None

        try:
            strategist_trade = self.contract.functions.strategistTrades(trade_id).call()
        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return strategist_trade

    ######################################################################
    # events & helpers
    ######################################################################

    # TODO: today the event signature is hardcoded, but we should be able to get it from the contract
    # the graph does something similar to this when you run codegen, it should be a simple string manipulation problem from the abis
    def get_log_strategist_trade_hash(self):
        return self.w3.keccak(
            text="LogStrategistTrade(uint256,bytes32,bytes32,address,address,uint256,address)"
        ).hex()

    # TODO: determine if this is the right assumtption to make about how the data is being received
    # this assumes that the function is being used directly in the context of being passed raw data from a websocket stream that has been loaded and converted to an AttributeDict
    # TODO: i feel like this could be done much faster... tracing will tell us
    def stream_log_strategist_trade(self, data):
        # load the data into an attribute dictionary that web3 can use
        # data = AttributeDict(json.loads(data))

        # convert the topics, transaction hash, and block hash to hex strings
        data["params"]["result"]["topics"] = [
            hexbytes.HexBytes(topic) for topic in data["params"]["result"]["topics"]
        ]
        data["params"]["result"]["transactionHash"] = hexbytes.HexBytes(
            data["params"]["result"]["transactionHash"]
        )
        data["params"]["result"]["blockHash"] = hexbytes.HexBytes(
            data["params"]["result"]["blockHash"]
        )

        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_strategist_trade_abi, data["params"]["result"]
            )

            # decode the offer id
            # TODO: there is probably a way to do this that does not hardcode the type of the id
            trade_id = decode(["uint256"], event["args"]["strategistTradeID"])[0]

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "id": trade_id,
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "ask_id": event["args"]["askId"],
                "bid_id": event["args"]["bidId"],
                "ask_asset": event["args"]["askAsset"],
                "bid_asset": event["args"]["bidAsset"],
                "timestamp": event["args"]["timestamp"],
                "owner": event["args"]["strategist"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    def parse_log_strategist_trade(self, log):
        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_strategist_trade_abi, log["params"]["result"]
            )

            # decode the offer id
            # TODO: there is probably a way to do this that does not hardcode the type of the id
            trade_id = decode(["uint256"], event["args"]["strategistTradeID"])[0]

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "id": trade_id,
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "ask_id": event["args"]["askId"],
                "bid_id": event["args"]["bidId"],
                "ask_asset": event["args"]["askAsset"],
                "bid_asset": event["args"]["bidAsset"],
                "timestamp": event["args"]["timestamp"],
                "owner": event["args"]["strategist"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    """
    event LogScrubbedStratTrade(
        uint256 strategistIDScrubbed,
        uint256 assetFill,
        address bathAssetAddress,
        uint256 quoteFill,
        address quoteAddress
    );
    """

    # TODO: today the event signature is hardcoded, but we should be able to get it from the contract
    # the graph does something similar to this when you run codegen, it should be a simple string manipulation problem from the abis
    def get_log_scrubbed_strat_trade_hash(self):
        return self.w3.keccak(
            text="LogStrategistTrade(uint256,uint256,address,uint256,address)"
        ).hex()

    # TODO: determine if this is the right assumtption to make about how the data is being received
    # this assumes that the function is being used directly in the context of being passed raw data from a websocket stream that has been loaded and converted to an AttributeDict
    # TODO: i feel like this could be done much faster... tracing will tell us
    def stream_log_scrubbed_strat_trade(self, data):
        # load the data into an attribute dictionary that web3 can use
        # data = AttributeDict(json.loads(data))

        # convert the topics, transaction hash, and block hash to hex strings
        data["params"]["result"]["topics"] = [
            hexbytes.HexBytes(topic) for topic in data["params"]["result"]["topics"]
        ]
        data["params"]["result"]["transactionHash"] = hexbytes.HexBytes(
            data["params"]["result"]["transactionHash"]
        )
        data["params"]["result"]["blockHash"] = hexbytes.HexBytes(
            data["params"]["result"]["blockHash"]
        )

        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_scrubbed_strat_trade_abi, data["params"]["result"]
            )

            # decode the offer id
            # TODO: there is probably a way to do this that does not hardcode the type of the id
            trade_id = decode(["uint256"], event["args"]["strategistIDScrubbed"])[0]

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "id": trade_id,
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "asset_fill": event["args"]["assetFill"],
                "bath_asset_address": event["args"]["bathAssetAddress"],
                "quote_fill": event["args"]["quoteFill"],
                "quote_address": event["args"]["quoteAddress"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    def parse_log_scrubbed_strat_trade(self, log):
        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_scrubbed_strat_trade_abi, log["params"]["result"]
            )

            # decode the offer id
            # TODO: there is probably a way to do this that does not hardcode the type of the id
            trade_id = decode(["uint256"], event["args"]["strategistIDScrubbed"])[0]

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "id": trade_id,
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "asset_fill": event["args"]["assetFill"],
                "bath_asset_address": event["args"]["bathAssetAddress"],
                "quote_fill": event["args"]["quoteFill"],
                "quote_address": event["args"]["quoteAddress"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    """
    event LogStrategistRewardClaim(
        address strategist,
        address asset,
        uint256 amountOfReward,
        uint256 timestamp
    );
    """

    """
    event LogBatchMarketMakingTrades(
        address strategist, 
        uint256[] trades
    );
    """

    # TODO: today the event signature is hardcoded, but we should be able to get it from the contract
    # the graph does something similar to this when you run codegen, it should be a simple string manipulation problem from the abis
    def get_log_batch_market_making_trades_hash(self):
        return self.w3.keccak(
            text="LogBatchMarketMakingTrades(address,uint256[])"
        ).hex()

    # TODO: determine if this is the right assumtption to make about how the data is being received
    # this assumes that the function is being used directly in the context of being passed raw data from a websocket stream that has been loaded and converted to an AttributeDict
    # TODO: i feel like this could be done much faster... tracing will tell us
    def stream_log_batch_market_making_trades(self, data):
        # load the data into an attribute dictionary that web3 can use
        # data = AttributeDict(json.loads(data))

        # convert the topics, transaction hash, and block hash to hex strings
        data["params"]["result"]["topics"] = [
            hexbytes.HexBytes(topic) for topic in data["params"]["result"]["topics"]
        ]
        data["params"]["result"]["transactionHash"] = hexbytes.HexBytes(
            data["params"]["result"]["transactionHash"]
        )
        data["params"]["result"]["blockHash"] = hexbytes.HexBytes(
            data["params"]["result"]["blockHash"]
        )

        # get the event data from the log
        try:
            event = get_event_data(
                self.codec,
                self.log_batch_market_making_trades_abi,
                data["params"]["result"],
            )

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "strategist": event["args"]["strategist"],
                "trades": event["args"]["trades"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    def parse_log_batch_market_making_trades(self, log):
        # get the event data from the log
        try:
            event = get_event_data(
                self.codec,
                self.log_batch_market_making_trades_abi,
                log["params"]["result"],
            )

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "strategist": event["args"]["strategist"],
                "trades": event["args"]["trades"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    """
        event LogRequote(
        address strategist,
        uint256 scrubbedOfferID,
        uint256 newOfferID
    );
    """

    # TODO: today the event signature is hardcoded, but we should be able to get it from the contract
    # the graph does something similar to this when you run codegen, it should be a simple string manipulation problem from the abis
    def get_log_requote_hash(self):
        return self.w3.keccak(text="LogRequote(address,uint256,uint256)").hex()

    # TODO: determine if this is the right assumtption to make about how the data is being received
    # this assumes that the function is being used directly in the context of being passed raw data from a websocket stream that has been loaded and converted to an AttributeDict
    # TODO: i feel like this could be done much faster... tracing will tell us
    def stream_log_requote(self, data):
        # load the data into an attribute dictionary that web3 can use
        # data = AttributeDict(json.loads(data))

        # convert the topics, transaction hash, and block hash to hex strings
        data["params"]["result"]["topics"] = [
            hexbytes.HexBytes(topic) for topic in data["params"]["result"]["topics"]
        ]
        data["params"]["result"]["transactionHash"] = hexbytes.HexBytes(
            data["params"]["result"]["transactionHash"]
        )
        data["params"]["result"]["blockHash"] = hexbytes.HexBytes(
            data["params"]["result"]["blockHash"]
        )

        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_requote_abi, data["params"]["result"]
            )

            # decode the offer id
            # TODO: there is probably a way to do this that does not hardcode the type of the id
            scrub_trade_id = decode(["uint256"], event["args"]["scrubbedOfferID"])[0]
            trade_id = decode(["uint256"], event["args"]["newOfferID"])[0]

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "id": trade_id,
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "strategist": event["args"]["strategist"],
                "scrub_trade_id": scrub_trade_id,
                "trade_id": trade_id,
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    def parse_log_requote(self, log):
        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_requote_abi, log["params"]["result"]
            )

            # decode the offer id
            # TODO: there is probably a way to do this that does not hardcode the type of the id
            scrub_trade_id = decode(["uint256"], event["args"]["scrubbedOfferID"])[0]
            trade_id = decode(["uint256"], event["args"]["newOfferID"])[0]

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "id": trade_id,
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "strategist": event["args"]["strategist"],
                "scrub_trade_id": scrub_trade_id,
                "trade_id": trade_id,
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    """
    event LogBatchRequoteOffers(address strategist, uint256[] scrubbedOfferIDs);
    """

    # TODO: today the event signature is hardcoded, but we should be able to get it from the contract
    # the graph does something similar to this when you run codegen, it should be a simple string manipulation problem from the abis
    def get_log_batch_requote_offers_hash(self):
        return self.w3.keccak(text="LogBatchRequoteOffers(address,uint256[])").hex()

    # TODO: determine if this is the right assumtption to make about how the data is being received
    # this assumes that the function is being used directly in the context of being passed raw data from a websocket stream that has been loaded and converted to an AttributeDict
    # TODO: i feel like this could be done much faster... tracing will tell us
    def stream_log_batch_requote_offers(self, data):
        # load the data into an attribute dictionary that web3 can use
        # data = AttributeDict(json.loads(data))

        # convert the topics, transaction hash, and block hash to hex strings
        data["params"]["result"]["topics"] = [
            hexbytes.HexBytes(topic) for topic in data["params"]["result"]["topics"]
        ]
        data["params"]["result"]["transactionHash"] = hexbytes.HexBytes(
            data["params"]["result"]["transactionHash"]
        )
        data["params"]["result"]["blockHash"] = hexbytes.HexBytes(
            data["params"]["result"]["blockHash"]
        )

        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_batch_requote_offers_abi, data["params"]["result"]
            )

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "strategist": event["args"]["strategist"],
                "scrub_trade_id": event["args"]["scrubbedOfferIDs"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    def parse_log_batch_requote_offers(self, log):
        # get the event data from the log
        try:
            event = get_event_data(
                self.codec, self.log_batch_requote_offers_abi, log["params"]["result"]
            )

            # now pass an offer back in the form of a dictionary
            # TODO: this is probably not the most performant, we will optimize later
            trade = {
                "txn": event["transactionHash"].hex(),
                "event": event["event"],
                "strategist": event["args"]["strategist"],
                "scrub_trade_id": event["args"]["scrubbedOfferIDs"],
            }
            return trade

        except Exception as e:
            log.error(e, exc_info=True)
            return None


class MarketAidSigner(MarketAid):
    """this class represents a MarketAid.sol contract with read and write functionality. it inherits from the MarketAid class and adds the ability to sign transactions.

    :param w3: a web3 instance
    :type w3: Web3
    :param address: the address of the contract
    :type address: str
    :param wallet: the signers wallet address
    :type wallet: str
    :param key: the signers private key
    :type key: str
    :param contract: an optional parameter that allows you to pass in a contract instance, if none the contract will be instantiated from the rolodex.py file
    :type contract_address: str, optional
    """

    def __init__(self, w3, address, wallet, key, contract=None):
        super().__init__(w3, address, contract)
        self.wallet = wallet
        self.key = key

    ######################################################################
    # write calls
    ######################################################################

    # adminMaxApproveTarget(target (address), token (address))
    def admin_max_approve_target(
        self, target, token, nonce=None, gas=3000000, gas_price=None
    ):
        """this function sets the max approval for a target address to spend a token on behalf of the contract

        :param target: the address of the target
        :type target: str
        :param token: the address of the token
        :type token: str
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the approval transaction, returns None if the transaction fails
        :rtype: dict
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            max_approve = self.contract.functions.adminMaxApproveTarget(
                target, token
            ).build_transaction(txn)
            max_approve = self.w3.eth.account.sign_transaction(max_approve, self.key)
            self.w3.eth.send_raw_transaction(max_approve.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(max_approve.hash)["status"]
                    == 0
                ):
                    log.error(
                        f"admin_max_approve_target transaction {max_approve.hash.hex()} failed"
                    )
                    raise SystemExit()

        except ValueError:
            log.warning(
                "most likely a checksum error... retrying with checksummed addresses"
            )
            max_approve = self.contract.functions.adminMaxApproveTarget(
                self.w3.to_checksum_address(target), self.w3.to_checksum_address(token)
            ).build_transaction(txn)
            max_approve = self.w3.eth.account.sign_transaction(max_approve, self.key)
            self.w3.eth.send_raw_transaction(max_approve.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(max_approve.hash)["status"]
                    == 0
                ):
                    log.error(
                        f"admin_max_approve_target transaction {max_approve.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return max_approve

    # adminPullAllFunds(erc20s address[])
    def admin_pull_all_funds(self, erc20s, nonce=None, gas=3000000, gas_price=None):
        """this function pulls all funds from the contract

        :param erc20s: a list of erc20 addresses
        :type erc20s: list
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the pull transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            pull_all_funds = self.contract.functions.adminPullAllFunds(
                erc20s
            ).build_transaction(txn)
            pull_all_funds = self.w3.eth.account.sign_transaction(
                pull_all_funds, self.key
            )
            self.w3.eth.send_raw_transaction(pull_all_funds.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(pull_all_funds.hash)[
                        "status"
                    ]
                    == 0
                ):
                    log.error(
                        f"admin_pull_all_funds transaction {pull_all_funds.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

    # adminRebalanceFunds(assetToSell (address), amountToSell (uint256), assetToTarget (address))
    def admin_rebalance_funds(
        self,
        asset_to_sell,
        amount_to_sell,
        asset_to_target,
        nonce=None,
        gas=3000000,
        gas_price=None,
    ):
        """this function rebalances funds from one asset to another on the RubiconMarket

        :param asset_to_sell: the address of the asset to sell
        :type asset_to_sell: str
        :param amount_to_sell: the amount of the asset to sell
        :type amount_to_sell: int
        :param asset_to_target: the address of the asset to target
        :type asset_to_target: str
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the rebalance transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            rebalance = self.contract.functions.adminRebalanceFunds(
                asset_to_sell, amount_to_sell, asset_to_target
            ).build_transaction(txn)
            rebalance = self.w3.eth.account.sign_transaction(rebalance, self.key)
            self.w3.eth.send_raw_transaction(rebalance.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(rebalance.hash)["status"]
                    == 0
                ):
                    log.error(
                        f"admin_rebalance_funds transaction {rebalance.hash.hex()} failed"
                    )
                    raise SystemExit()

        except ValueError:
            log.warning(
                "most likely a checksum error... retrying with checksummed addresses"
            )
            rebalance = self.contract.functions.adminRebalanceFunds(
                self.w3.to_checksum_address(asset_to_sell),
                amount_to_sell,
                self.w3.to_checksum_address(asset_to_target),
            ).build_transaction(txn)
            rebalance = self.w3.eth.account.sign_transaction(rebalance, self.key)
            self.w3.eth.send_raw_transaction(rebalance.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(rebalance.hash)["status"]
                    == 0
                ):
                    log.error(
                        f"admin_rebalance_funds transaction {rebalance.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return rebalance

    # approveStrategist(strategist (address))
    def approve_strategist(self, strategist, nonce=None, gas=3000000, gas_price=None):
        """this function approves a strategist to use the aid contract instance

        :param strategist: the address of the strategist to approve
        :type strategist: str
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the approve transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            approve = self.contract.functions.approveStrategist(
                strategist
            ).build_transaction(txn)
            approve = self.w3.eth.account.sign_transaction(approve, self.key)
            self.w3.eth.send_raw_transaction(approve.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(approve.hash)["status"]
                    == 0
                ):
                    log.error(
                        f"approve_strategist transaction {approve.hash.hex()} failed"
                    )
                    raise SystemExit()

        except ValueError:
            log.warning(
                "most likely a checksum error... retrying with checksummed addresses"
            )
            approve = self.contract.functions.approveStrategist(
                self.w3.to_checksum_address(strategist)
            ).build_transaction(txn)
            approve = self.w3.eth.account.sign_transaction(approve, self.key)
            self.w3.eth.send_raw_transaction(approve.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(approve.hash)["status"]
                    == 0
                ):
                    log.error(
                        f"approve_strategist transaction {approve.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return approve

    # batchMarketMakingTrades(tokenPairs (address[2]), askNumerators (uint256[]), askDenominators (uint256[]), bidNumerators (uint256[]), bidDenominators (uint256[]))
    def batch_market_making_trades(
        self,
        token_pairs,
        ask_numerators,
        ask_denominators,
        bid_numerators,
        bid_denominators,
        nonce=None,
        gas=3000000,
        gas_price=None,
    ):
        """this function executes a batch of market making trades on the RubiconMarket

        :param token_pairs: the token pairs to trade [token0, token1]
        :type token_pairs: list
        :param ask_numerators: the numerators of the ask prices
        :type ask_numerators: list
        :param ask_denominators: the denominators of the ask prices
        :type ask_denominators: list
        :param bid_numerators: the numerators of the bid prices
        :type bid_numerators: list
        :param bid_denominators: the denominators of the bid prices
        :type bid_denominators: list
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the batch market making trades transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            batch = self.contract.functions.batchMarketMakingTrades(
                token_pairs,
                ask_numerators,
                ask_denominators,
                bid_numerators,
                bid_denominators,
            ).build_transaction(txn)
            batch = self.w3.eth.account.sign_transaction(batch, self.key)
            self.w3.eth.send_raw_transaction(batch.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(batch.hash)["status"] == 0:
                    log.error(
                        f"batch_market_making_trades transaction {batch.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return batch

    # batchRequoteAllOffers(tokenPair (address[2]), askNumerators (uint256[]), askDenominators (uint256[]), bidNumerators (uint256[]), bidDenominators (uint256[]))
    def batch_requote_all_offers(
        self,
        token_pair,
        ask_numerators,
        ask_denominators,
        bid_numerators,
        bid_denominators,
        nonce=None,
        gas=3000000,
        gas_price=None,
    ):
        """this function executes a batch requote while clearing all offers the strategist has on the RubiconMarket

        :param token_pair: the token pair to trade [token0, token1]
        :type token_pair: list
        :param ask_numerators: the numerators of the ask prices
        :type ask_numerators: list
        :param ask_denominators: the denominators of the ask prices
        :type ask_denominators: list
        :param bid_numerators: the numerators of the bid prices
        :type bid_numerators: list
        :param bid_denominators: the denominators of the bid prices
        :type bid_denominators: list
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the batch requote all offers transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            batch = self.contract.functions.batchRequoteAllOffers(
                token_pair,
                ask_numerators,
                ask_denominators,
                bid_numerators,
                bid_denominators,
            ).build_transaction(txn)
            batch = self.w3.eth.account.sign_transaction(batch, self.key)
            self.w3.eth.send_raw_transaction(batch.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(batch.hash)["status"] == 0:
                    log.error(
                        f"batch_requote_all_offers transaction {batch.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return batch

    # batchRequoteOffers(ids (uint256[]), tokenPair (address[2]), askNumerators (uint256[]), askDenominators (uint256[]), bidNumerators (uint256[]), bidDenominators (uint256[]))
    def batch_requote_offers(
        self,
        ids,
        token_pair,
        ask_numerators,
        ask_denominators,
        bid_numerators,
        bid_denominators,
        nonce=None,
        gas=3000000,
        gas_price=None,
    ):
        """this function executes a batch requote of all offers that are provided in the ids array

        :param ids: the ids of the offers to requote
        :type ids: list
        :param token_pair: the token pair to trade [token0, token1]
        :type token_pair: list
        :param ask_numerators: the numerators of the ask prices
        :type ask_numerators: list
        :param ask_denominators: the denominators of the ask prices
        :type ask_denominators: list
        :param bid_numerators: the numerators of the bid prices
        :type bid_numerators: list
        :param bid_denominators: the denominators of the bid prices
        :type bid_denominators: list
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the batch requote offers transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            batch = self.contract.functions.batchRequoteOffers(
                ids,
                token_pair,
                ask_numerators,
                ask_denominators,
                bid_numerators,
                bid_denominators,
            ).build_transaction(txn)
            batch = self.w3.eth.account.sign_transaction(batch, self.key)
            self.w3.eth.send_raw_transaction(batch.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(batch.hash)["status"] == 0:
                    log.error(
                        f"batch_requote_offers transaction {batch.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return batch

    # placeMarketMakingTrades(tokenPair (address[2]), askNumerator (uint256), askDenominator (uint256), bidNumerator (uint256), bidDenominator (uint256))
    # aid.batch_market_making_trades([weth.address, usdc.address], [the amount of the asset you will sell], [the amount of the quote you will receive], [the amount of quote you will pay], [the amount of asset you would receive])
    def place_market_making_trades(
        self,
        token_pair,
        ask_numerator,
        ask_denominator,
        bid_numerator,
        bid_denominator,
        nonce=None,
        gas=3000000,
        gas_price=None,
    ):
        """this function executes a market making trade on the RubiconMarket

        :param token_pair: the token pair to trade [token0, token1]
        :type token_pair: list
        :param ask_numerator: the numerator of the ask price. this is the amount of the asset you will sell
        :type ask_numerator: int
        :param ask_denominator: the denominator of the ask price. this is the amount of the quote you will receive
        :type ask_denominator: int
        :param bid_numerator: the numerator of the bid price. this is the amount of quote you will pay
        :type bid_numerator: int
        :param bid_denominator: the denominator of the bid price. this is the amount of asset you would receive
        :type bid_denominator: int
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the place market making trades transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            trade = self.contract.functions.placeMarketMakingTrades(
                token_pair,
                ask_numerator,
                ask_denominator,
                bid_numerator,
                bid_denominator,
            ).build_transaction(txn)
            trade = self.w3.eth.account.sign_transaction(trade, self.key)
            self.w3.eth.send_raw_transaction(trade.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(trade.hash)["status"] == 0:
                    log.error(
                        f"place_market_making_trades transaction {trade.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return trade

    # removeStrategist(strategist (address))
    def remove_strategist(self, strategist, nonce=None, gas=3000000, gas_price=None):
        """this function removes a strategist from the approved strategist list on the market aid contract

        :param strategist: the address of the strategist to remove
        :type strategist: str
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the remove strategist transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            remove = self.contract.functions.removeStrategist(
                strategist
            ).build_transaction(txn)
            remove = self.w3.eth.account.sign_transaction(remove, self.key)
            self.w3.eth.send_raw_transaction(remove.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(remove.hash)["status"] == 0:
                    log.error(
                        f"remove_strategist transaction {remove.hash.hex()} failed"
                    )
                    raise SystemExit()

        except ValueError:
            log.warning(
                "most likely a checksum error... retrying with checksummed addresses"
            )
            remove = self.contract.functions.removeStrategist(
                self.w3.to_checksum_address(strategist)
            ).build_transaction(txn)
            remove = self.w3.eth.account.sign_transaction(remove, self.key)
            self.w3.eth.send_raw_transaction(remove.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(remove.hash)["status"] == 0:
                    log.error(
                        f"remove_strategist transaction {remove.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return remove

    # requote(id (uint256), tokenPair (address[2]), askNumerator (uint256), askDenominator (uint256), bidNumerator (uint256), bidDenominator (uint256))
    def requote(
        self,
        id,
        token_pair,
        ask_numerator,
        ask_denominator,
        bid_numerator,
        bid_denominator,
        nonce=None,
        gas=3000000,
        gas_price=None,
    ):
        """this function requotes an offer on the RubiconMarket

        :param id: the id of the offer to requote
        :type id: int
        :param token_pair: the token pair to trade [token0, token1]
        :type token_pair: list
        :param ask_numerator: the numerator of the ask price
        :type ask_numerator: int
        :param ask_denominator: the denominator of the ask price
        :type ask_denominator: int
        :param bid_numerator: the numerator of the bid price
        :type bid_numerator: int
        :param bid_denominator: the denominator of the bid price
        :type bid_denominator: int
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the requote transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            requote = self.contract.functions.requote(
                id,
                token_pair,
                ask_numerator,
                ask_denominator,
                bid_numerator,
                bid_denominator,
            ).build_transaction(txn)
            requote = self.w3.eth.account.sign_transaction(requote, self.key)
            self.w3.eth.send_raw_transaction(requote.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if (
                    self.w3.eth.wait_for_transaction_receipt(requote.hash)["status"]
                    == 0
                ):
                    log.error(f"requote transaction {requote.hash.hex()} failed")
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return requote

    # scrubStrategistTrade(id (uint256))
    def scrub_strategist_trade(self, id, nonce=None, gas=3000000, gas_price=None):
        """this function scrubs a strategist trade from the RubiconMarket contract

        :param id: the id of the trade to scrub
        :type id: int
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            scrub = self.contract.functions.scrubStrategistTrade(id).build_transaction(
                txn
            )
            scrub = self.w3.eth.account.sign_transaction(scrub, self.key)
            self.w3.eth.send_raw_transaction(scrub.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(scrub.hash)["status"] == 0:
                    log.error(
                        f"scrub_strategist_trade transaction {scrub.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return scrub

    # scrubStrategistTrades(ids (uint256[]))
    def scrub_strategist_trades(self, ids, nonce=None, gas=3000000, gas_price=None):
        """this function scrubs a list of strategist trades from the RubiconMarket contract

        :param ids: the ids of the trades to scrub
        :type ids: list
        :param nonce: nonce of the transaction, defaults to calling the chain state to get the nonce
        :type nonce: int, optional
        :param gas: gas limit of the transaction, defaults to a value of 3000000
        :type gas: int, optional
        :param gas_price: gas price of the transaction, defaults to the gas price of the chain
        :type gas_price: int, optional
        :return: the transaction object of the scrub transaction, returns None if the transaction fails
        :rtype: dict, None
        """

        if nonce is None:
            txn_nonce = self.w3.eth.get_transaction_count(self.wallet)
        else:
            txn_nonce = nonce

        if gas_price is None:
            gas_price = self.w3.eth.gas_price

        txn = {
            "chainId": self.chain,
            "gas": gas,
            "gasPrice": gas_price,
            "nonce": txn_nonce,
        }

        try:
            scrub = self.contract.functions.scrubStrategistTrades(
                ids
            ).build_transaction(txn)
            scrub = self.w3.eth.account.sign_transaction(scrub, self.key)
            self.w3.eth.send_raw_transaction(scrub.rawTransaction)

            # if a user is not providing a nonce, wait for the transaction to either be confirmed or rejected before continuing
            if nonce is None:
                if self.w3.eth.wait_for_transaction_receipt(scrub.hash)["status"] == 0:
                    log.error(
                        f"scrub_strategist_trades transaction {scrub.hash.hex()} failed"
                    )
                    raise SystemExit()

        except Exception as e:
            log.error(e, exc_info=True)
            return None

        return scrub
