def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='oasis-market-maker-cancel') parser.add_argument("--endpoint-uri", type=str, help="JSON-RPC uri (example: `http://localhost:8545`)") parser.add_argument("--rpc-host", default="localhost", type=str, help="[DEPRECATED] JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", default=8545, type=int, help="[DEPRECATED] JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", help="JSON-RPC timeout (in seconds, default: 10)", default=10, type=int) parser.add_argument("--eth-from", help="Ethereum account from which to send transactions", required=True, type=str) parser.add_argument("--eth-key", type=str, nargs='*', help="Ethereum private key(s) to use") parser.add_argument("--oasis-address", help="Ethereum address of the OasisDEX contract", required=True, type=str) parser.add_argument("--gas-price", help="Gas price in Wei (default: node default)", default=0, type=int) self.arguments = parser.parse_args(args) if 'web3' in kwargs: self.web3 = kwargs['web3'] elif self.arguments.endpoint_uri: self.web3: Web3 = web3_via_http(self.arguments.endpoint_uri, self.arguments.rpc_timeout) else: self.web3 = Web3(HTTPProvider(endpoint_uri=f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}", request_kwargs={"timeout": self.arguments.rpc_timeout})) self.web3.eth.defaultAccount = self.arguments.eth_from self.our_address = Address(self.arguments.eth_from) register_keys(self.web3, self.arguments.eth_key) self.otc = MatchingMarket(web3=self.web3, address=Address(self.arguments.oasis_address)) logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.INFO)
def test_connect_to_testchain(self, our_address): uri = "http://0.0.0.0:8545" web3 = web3_via_http(uri, 63, 39) assert isinstance(web3.provider, HTTPProvider) assert web3.provider._request_kwargs['timeout'] == 63 for adapter in _get_session(uri).adapters.values(): assert adapter._pool_connections == 39 assert adapter._pool_maxsize == 39 assert isinstance(web3, Web3) assert eth_balance(web3, our_address) > Wad(0)
def web3(): # These details are specific to the MCD testchain used for pymaker unit tests. web3 = web3_via_http("http://0.0.0.0:8545", 3, 100) web3.eth.defaultAccount = "0x50FF810797f75f6bfbf2227442e0c961a8562F4C" register_keys(web3, [ "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key1.json,pass_file=/dev/null", "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key2.json,pass_file=/dev/null", "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key3.json,pass_file=/dev/null", "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key4.json,pass_file=/dev/null", "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key.json,pass_file=/dev/null" ]) # reduce logspew logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("urllib3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) return web3
def web3() -> Web3: # for local dockerized parity testchain web3 = web3_via_http("http://0.0.0.0:8545") web3.eth.defaultAccount = "0x50FF810797f75f6bfbf2227442e0c961a8562F4C" register_keys(web3, ["key_file=tests/config/keys/UnlimitedChain/key1.json,pass_file=/dev/null", "key_file=tests/config/keys/UnlimitedChain/key2.json,pass_file=/dev/null", "key_file=tests/config/keys/UnlimitedChain/key3.json,pass_file=/dev/null", "key_file=tests/config/keys/UnlimitedChain/key4.json,pass_file=/dev/null", "key_file=tests/config/keys/UnlimitedChain/key.json,pass_file=/dev/null"]) # reduce logspew logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("urllib3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) assert len(web3.eth.accounts) > 3 return web3
def test_unsupported_url(self): with pytest.raises(ValueError): web3_via_http("wss://0.0.0.0:8545")
def __init__(self, args: list, **kwargs): """Pass in arguements assign necessary variables/objects and instantiate other Classes""" parser = argparse.ArgumentParser("chief-keeper") parser.add_argument( "--rpc-host", type=str, default="https://localhost:8545", help="JSON-RPC host:port (default: 'localhost:8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--network", type=str, required=True, help= "Network that you're running the Keeper on (options, 'mainnet', 'kovan', 'testnet')" ) parser.add_argument( "--eth-from", type=str, required=True, help= "Ethereum address from which to send transactions; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=/path/to/keystore.json,pass_file=/path/to/passphrase.txt')" ) parser.add_argument( "--dss-deployment-file", type=str, required=False, help= "Json description of all the system addresses (e.g. /Full/Path/To/configFile.json)" ) parser.add_argument( "--chief-deployment-block", type=int, required=False, default=0, help= " Block that the Chief from dss-deployment-file was deployed at (e.g. 8836668" ) parser.add_argument( "--max-errors", type=int, default=100, help= "Maximum number of allowed errors before the keeper terminates (default: 100)" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") parser.add_argument("--ethgasstation-api-key", type=str, default=None, help="ethgasstation API key") parser.add_argument("--gas-initial-multiplier", type=str, default=1.0, help="ethgasstation API key") parser.add_argument("--gas-reactive-multiplier", type=str, default=2.25, help="gas strategy tuning") parser.add_argument("--gas-maximum", type=str, default=5000, help="gas strategy tuning") parser.set_defaults(cageFacilitated=False) self.arguments = parser.parse_args(args) self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else web3_via_http( endpoint_uri=self.arguments.rpc_host, timeout=self.arguments.rpc_timeout, http_pool_size=100) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) if self.arguments.dss_deployment_file: self.dss = DssDeployment.from_json( web3=self.web3, conf=open(self.arguments.dss_deployment_file, "r").read()) else: self.dss = DssDeployment.from_network( web3=self.web3, network=self.arguments.network) self.deployment_block = self.arguments.chief_deployment_block self.max_errors = self.arguments.max_errors self.errors = 0 self.confirmations = 0 # Create dynamic gas strategy if self.arguments.ethgasstation_api_key: self.gas_price = DynamicGasPrice(self.arguments, self.web3) else: self.gas_price = DefaultGasPrice() logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO))
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='auction-keeper') parser.add_argument( "--rpc-host", type=str, default="http://localhost:8545", help= "JSON-RPC endpoint URI with port (default: `http://localhost:8545')" ) parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to send transactions") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument('--type', type=str, choices=['clip', 'flip', 'flap', 'flop'], help="Auction type in which to participate") parser.add_argument( '--ilk', type=str, help= "Name of the collateral type for a clip or flip keeper (e.g. 'ETH-B', 'ZRX-A'); " "available collateral types can be found at the left side of the Oasis Borrow" ) parser.add_argument( '--bid-only', dest='create_auctions', action='store_false', help="Do not take opportunities to create new auctions") parser.add_argument('--kick-only', dest='bid_on_auctions', action='store_false', help="Do not bid on auctions") parser.add_argument( '--deal-for', type=str, nargs="+", help="List of addresses for which auctions will be dealt") parser.add_argument('--min-auction', type=int, default=1, help="Lowest auction id to consider") parser.add_argument( '--max-auctions', type=int, default=1000, help="Maximum number of auctions to simultaneously interact with, " "used to manage OS and hardware limitations") parser.add_argument( '--min-collateral-lot', type=float, default=0, help= "Minimum lot size to create or bid upon/take from a collateral auction" ) parser.add_argument( '--bid-check-interval', type=float, default=4.0, help= "Period of timer [in seconds] used to check bidding models for changes" ) parser.add_argument( '--bid-delay', type=float, default=0.0, help= "Seconds to wait between bids, used to manage OS and hardware limitations" ) parser.add_argument( '--shard-id', type=int, default=0, help= "When sharding auctions across multiple keepers, this identifies the shard" ) parser.add_argument( '--shards', type=int, default=1, help= "Number of shards; should be one greater than your highest --shard-id" ) parser.add_argument( '--from-block', type=int, help= "Starting block from which to find vaults to bite or debt to queue " "(set to block where MCD was deployed)") parser.add_argument( '--chunk-size', type=int, default=20000, help= "When batching chain history requests, this is the number of blocks for each request" ) parser.add_argument( "--tokenflow-url", type=str, help= "When specified, urn history will be initialized using the TokenFlow API" ) parser.add_argument("--tokenflow-key", type=str, help="API key for the TokenFlow endpoint") parser.add_argument( "--vulcanize-endpoint", type=str, help= "When specified, urn history will be initialized from a VulcanizeDB node" ) parser.add_argument("--vulcanize-key", type=str, help="API key for the Vulcanize endpoint") parser.add_argument( '--vat-dai-target', type=str, help= "Amount of Dai to keep in the Vat contract or ALL to join entire token balance" ) parser.add_argument( '--keep-dai-in-vat-on-exit', dest='exit_dai_on_shutdown', action='store_false', help= "Retain Dai in the Vat on exit, saving gas when restarting the keeper" ) parser.add_argument('--keep-gem-in-vat-on-exit', dest='exit_gem_on_shutdown', action='store_false', help="Retain collateral in the Vat on exit") parser.add_argument( '--return-gem-interval', type=int, default=300, help= "Period of timer [in seconds] used to check and exit won collateral" ) parser.add_argument( "--model", type=str, nargs='+', help="Commandline to use in order to start the bidding model") parser.add_argument( "--oracle-gas-price", action='store_true', help="Use a fast gas price aggregated across multiple oracles") parser.add_argument("--ethgasstation-api-key", type=str, default=None, help="EthGasStation API key") parser.add_argument("--etherscan-api-key", type=str, default=None, help="Etherscan API key") parser.add_argument("--blocknative-api-key", type=str, default=None, help="Blocknative API key") parser.add_argument( '--fixed-gas-price', type=float, default=None, help= "Uses a fixed value (in Gwei) instead of an external API to determine initial gas" ) parser.add_argument("--poanetwork-url", type=str, default=None, help="Alternative POANetwork URL") parser.add_argument( "--gas-initial-multiplier", type=float, default=1.0, help= "Adjusts the initial API-provided 'fast' gas price, default 1.0") parser.add_argument( "--gas-reactive-multiplier", type=float, default=1.125, help= "Increases gas price when transactions haven't been mined after some time" ) parser.add_argument( "--gas-maximum", type=float, default=2000, help= "Places an upper bound (in Gwei) on the amount of gas to use for a single TX" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) # Configure connection to the chain self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else web3_via_http( endpoint_uri=self.arguments.rpc_host, timeout=self.arguments.rpc_timeout, http_pool_size=100) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # Check configuration for retrieving urns/bites if self.arguments.type in ['clip', 'flip'] and self.arguments.create_auctions \ and self.arguments.from_block is None \ and self.arguments.tokenflow_url is None \ and self.arguments.vulcanize_endpoint is None: raise RuntimeError( "One of --from-block, --tokenflow_url, or --vulcanize-endpoint must be specified " "to bite and kick off new collateral auctions") if self.arguments.type in ['clip', 'flip'] and not self.arguments.ilk: raise RuntimeError( "--ilk must be supplied when configuring a collateral auction keeper" ) if self.arguments.type == 'flop' and self.arguments.create_auctions \ and self.arguments.from_block is None: raise RuntimeError( "--from-block must be specified to kick off flop auctions") # Configure core and token contracts self.mcd = DssDeployment.from_node(web3=self.web3) self.vat = self.mcd.vat self.vow = self.mcd.vow self.mkr = self.mcd.mkr self.dai_join = self.mcd.dai_adapter if self.arguments.type in ['clip', 'flip']: self.collateral = self.mcd.collaterals[self.arguments.ilk] self.ilk = self.collateral.ilk self.gem_join = self.collateral.adapter else: self.collateral = None self.ilk = None self.gem_join = None # Configure auction contracts self.auction_contract = self.get_contract() self.auction_type = None is_collateral_auction = False self.is_dealable = True self.urn_history = None if isinstance(self.auction_contract, Clipper): self.auction_type = 'clip' is_collateral_auction = True self.min_collateral_lot = Wad.from_number( self.arguments.min_collateral_lot) self.is_dealable = False self.strategy = ClipperStrategy(self.auction_contract, self.min_collateral_lot) elif isinstance(self.auction_contract, Flipper): self.auction_type = 'flip' is_collateral_auction = True self.min_collateral_lot = Wad.from_number( self.arguments.min_collateral_lot) self.strategy = FlipperStrategy(self.auction_contract, self.min_collateral_lot) elif isinstance(self.auction_contract, Flapper): self.auction_type = 'flap' self.strategy = FlapperStrategy(self.auction_contract, self.mkr.address) elif isinstance(self.auction_contract, Flopper): self.auction_type = 'flop' self.strategy = FlopperStrategy(self.auction_contract) else: raise RuntimeError( f"{self.auction_contract} auction contract is not supported") if is_collateral_auction and self.arguments.create_auctions: if self.arguments.vulcanize_endpoint: self.urn_history = VulcanizeUrnHistoryProvider( self.mcd, self.ilk, self.arguments.vulcanize_endpoint, self.arguments.vulcanize_key) elif self.arguments.tokenflow_url: self.urn_history = TokenFlowUrnHistoryProvider( self.web3, self.mcd, self.ilk, self.arguments.tokenflow_url, self.arguments.tokenflow_key, self.arguments.chunk_size) else: self.urn_history = ChainUrnHistoryProvider( self.web3, self.mcd, self.ilk, self.arguments.from_block, self.arguments.chunk_size) # Create the collection used to manage auctions relevant to this keeper if self.arguments.model: model_command = ' '.join(self.arguments.model) else: if self.arguments.bid_on_auctions: raise RuntimeError( "--model must be specified to bid on auctions") else: model_command = ":" self.auctions = Auctions(auction_contract=self.auction_contract, model_factory=ModelFactory(model_command)) self.auctions_lock = threading.Lock() # Since we don't want periodically-pollled bidding threads to back up, use a flag instead of a lock. self.is_joining_dai = False self.dead_since = {} self.lifecycle = None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO)) # Create gas strategy used for non-bids and bids which do not supply gas price self.gas_price = DynamicGasPrice(self.arguments, self.web3) # Configure account(s) for which we'll deal auctions self.deal_all = False self.deal_for = set() if self.is_dealable: if self.arguments.deal_for is None: self.deal_for.add(self.our_address) elif len( self.arguments.deal_for ) == 1 and self.arguments.deal_for[0].upper() in ["ALL", "NONE"]: if self.arguments.deal_for[0].upper() == "ALL": self.deal_all = True # else no auctions will be dealt elif len(self.arguments.deal_for) > 0: for account in self.arguments.deal_for: self.deal_for.add(Address(account)) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO)
Transact, Wad, web3_via_http from pymaker.gas import FixedGasPrice, GasStrategy, GeometricGasPrice from pymaker.keys import register_keys from pymaker.util import synchronize, bytes_to_hexstring logging.basicConfig( format='%(asctime)-15s [%(thread)d] %(levelname)-8s %(message)s', level=logging.DEBUG) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO) transact = False web3 = web3_via_http(endpoint_uri=os.environ['ETH_RPC_URL']) if len(sys.argv) > 1: web3.eth.defaultAccount = sys.argv[ 1] # ex: 0x0000000000000000000000000000000aBcdef123 if len(sys.argv) > 2: register_keys( web3, [sys.argv[2]] ) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass transact = True our_address = Address(web3.eth.defaultAccount) stuck_txes_to_submit = int(sys.argv[3]) if len(sys.argv) > 3 else 0 else: our_address = None stuck_txes_to_submit = 0 GWEI = 1000000000
from pymaker import Address, eth_transfer, web3_via_http from pymaker.gas import DefaultGasPrice, GeometricGasPrice from pymaker.lifecycle import Lifecycle from pymaker.keys import register_keys from pymaker.numeric import Wad from pymaker.token import EthToken logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO) endpoint_uri = sys.argv[1] web3 = web3_via_http(endpoint_uri, timeout=10) print(web3.clientVersion) """ Purpose: Tests pymaker on chains or layer-2s where multi-collateral Dai is not deployed. Argument: Reqd? Example: Ethereum node URI yes https://localhost:8545 Ethereum address no 0x0000000000000000000000000000000aBcdef123 Private key no key_file=~keys/default-account.json,pass_file=~keys/default-account.pass Gas tip (GWEI) no 9 """ if len(sys.argv) > 3: web3.eth.defaultAccount = sys.argv[2]
from pymaker import Address, web3_via_http from pymaker.deployment import DssDeployment from pymaker.gas import FixedGasPrice, GeometricGasPrice from pymaker.keys import register_keys from pymaker.numeric import Wad logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO) pool_size = int(sys.argv[3]) if len(sys.argv) > 3 else 10 web3 = web3_via_http(endpoint_uri=os.environ['ETH_RPC_URL'], http_pool_size=pool_size) web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass mcd = DssDeployment.from_node(web3) our_address = Address(web3.eth.defaultAccount) weth = DssDeployment.from_node(web3).collaterals['ETH-A'].gem GWEI = 1000000000 slow_gas = GeometricGasPrice(initial_price=int(0.8 * GWEI), every_secs=30, max_price=2000 * GWEI) fast_gas = GeometricGasPrice(initial_price=int(1.1 * GWEI), every_secs=30, max_price=2000 * GWEI) class TestApp: def main(self): self.test_replacement()
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='uniswap-market-maker-keeper') parser.add_argument( "--endpoint-uri", type=str, default="http://localhost:8545", help="JSON-RPC uri (default: `http://localhost:8545`)") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to send transactions") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument( "--pair", type=str, required=True, help="Token pair (sell/buy) on which the keeper will operate") parser.add_argument("--token-config", type=str, required=True, help="Token configuration file") parser.add_argument("--price-feed", type=str, required=True, help="Source of price feed") parser.add_argument( "--price-feed-accepted-delay", type=int, default=60, help= "Number of seconds the keeper will tolerate the price feed being null before removing liquidity" ) parser.add_argument( "--price-feed-expiry", type=int, default=86400, help="Maximum age of the price feed (in seconds, default: 86400)") parser.add_argument( "--max-add-liquidity-slippage", type=int, default=2, help= "Maximum percentage off the desired amount of liquidity to add in add_liquidity()" ) parser.add_argument( "--accepted-price-slippage-up", type=float, required=True, help= "Percentage difference between Uniswap exchange rate and aggregated price above which liquidity would be added" ) parser.add_argument( "--accepted-price-slippage-down", type=float, required=True, help= "Percentage difference between Uniswap exchange rate and aggregated price below which liquidity would be added" ) parser.add_argument("--target-a-min-balance", type=float, required=True, help="Minimum balance of token A to maintain.") parser.add_argument("--target-a-max-balance", type=float, required=True, help="Minimum balance of token A to maintain.") parser.add_argument("--target-b-min-balance", type=float, required=True, help="Minimum balance of token B to maintain.") parser.add_argument("--target-b-max-balance", type=float, required=True, help="Minimum balance of token B to maintain.") parser.add_argument( "--factory-address", type=str, default="0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", help= "Address of the UniswapV2 Factory smart contract used to create new pools" ) parser.add_argument( "--router-address", type=str, default="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", help= "Address of the UniswapV2 RouterV2 smart contract used to handle liquidity management" ) parser.add_argument( "--initial-delay", type=int, default=10, help="Initial number of seconds to wait before placing liquidity") parser.add_argument( '--staking-rewards-name', type=StakingRewardsName, choices=StakingRewardsName, help="Name of contract to stake liquidity tokens with") parser.add_argument( "--staking-rewards-contract-address", type=str, help="Address of contract to stake liquidity tokens with") parser.add_argument( "--staking-rewards-target-reward-amount", type=float, help="Address of contract to stake liquidity tokens with") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") add_gas_arguments(parser) self.arguments = parser.parse_args(args) setup_logging(self.arguments) self.web3: Web3 = web3_via_http(self.arguments.endpoint_uri, self.arguments.rpc_timeout) self.web3.eth.defaultAccount = self.arguments.eth_from self.our_address = Address(self.web3.eth.defaultAccount) if 'web3' not in kwargs: register_keys(self.web3, self.arguments.eth_key) self.gas_price = GasPriceFactory().create_gas_price( self.web3, self.arguments) # TODO: Add a more sophisticated regex for different variants of eth on the exchange # Record if eth is in pair, so can check which liquidity method needs to be used self.is_eth = 'ETH' in self.pair() # Identify which token is ETH, so we can provide the arguments to Uniswap Router in expected order self.eth_position = 1 if self.is_eth: self.eth_position = 0 if self.pair().split('-')[0] == 'ETH' else 1 self.reloadable_config = ReloadableConfig(self.arguments.token_config) self._last_config_dict = None self._last_config = None self.token_config = self.get_token_config().token_config self.token_a, self.token_b = self.instantiate_tokens(self.pair()) self.uniswap = UniswapV2(self.web3, self.token_a, self.token_b, self.our_address, Address(self.arguments.router_address), Address(self.arguments.factory_address)) # instantiate specific StakingRewards depending on arguments self.staking_rewards = StakingRewardsFactory().create_staking_rewards( self.arguments, self.web3) self.staking_rewards_target_reward_amount = self.arguments.staking_rewards_target_reward_amount # configure price feed self.price_feed = PriceFeedFactory().create_price_feed(self.arguments) self.price_feed_accepted_delay = self.arguments.price_feed_accepted_delay self.control_feed = create_control_feed(self.arguments) self.spread_feed = create_spread_feed(self.arguments) self.feed_price_null_counter = 0 # testing_feed_price is used by the integration tests in tests/test_uniswapv2.py, to test different pricing scenarios # as the keeper consistently checks the price, some long running state variable is needed to self.testing_feed_price = False self.test_price = Wad.from_number(0) # initalize uniswap price self.uniswap_current_exchange_price = self.uniswap.get_exchange_rate() # set target min and max amounts for each side of the pair # balance doesnt exceed some level, as an effective stop loss against impermanent loss self.target_a_min_balance = Wad.from_number( self.arguments.target_a_min_balance) self.target_a_max_balance = Wad.from_number( self.arguments.target_a_max_balance) self.target_b_min_balance = Wad.from_number( self.arguments.target_b_min_balance) self.target_b_max_balance = Wad.from_number( self.arguments.target_b_max_balance) self.accepted_price_slippage_up = Wad.from_number( self.arguments.accepted_price_slippage_up / 100) self.accepted_price_slippage_down = Wad.from_number( self.arguments.accepted_price_slippage_down / 100) self.max_add_liquidity_slippage = Wad.from_number( self.arguments.max_add_liquidity_slippage / 100)
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='oasis-market-maker-keeper') parser.add_argument( "--endpoint-uri", type=str, help="JSON-RPC uri (example: `http://localhost:8545`)") parser.add_argument( "--rpc-host", default="localhost", type=str, help="[DEPRECATED] JSON-RPC host (default: `localhost')") parser.add_argument( "--rpc-port", default=8545, type=int, help="[DEPRECATED] JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to send transactions") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument("--tub-address", type=str, required=False, help="Ethereum address of the Tub contract") parser.add_argument("--oasis-address", type=str, required=True, help="Ethereum address of the OasisDEX contract") parser.add_argument( "--oasis-support-address", type=str, required=False, help="Ethereum address of the OasisDEX support contract") parser.add_argument("--buy-token-address", type=str, required=True, help="Ethereum address of the buy token") parser.add_argument("--sell-token-address", type=str, required=True, help="Ethereum address of the sell token") parser.add_argument("--buy-token-name", type=str, required=True, help="Ethereum address of the buy token") parser.add_argument("--sell-token-name", type=str, required=True, help="Ethereum address of the sell token") parser.add_argument("--buy-token-decimals", type=int, required=True, help="Ethereum address of the buy token") parser.add_argument("--sell-token-decimals", type=int, required=True, help="Ethereum address of the sell token") parser.add_argument("--config", type=str, required=True, help="Bands configuration file") parser.add_argument("--price-feed", type=str, required=True, help="Source of price feed") parser.add_argument( "--price-feed-expiry", type=int, default=120, help="Maximum age of the price feed (in seconds, default: 120)") parser.add_argument("--spread-feed", type=str, help="Source of spread feed") parser.add_argument( "--spread-feed-expiry", type=int, default=3600, help="Maximum age of the spread feed (in seconds, default: 3600)") parser.add_argument("--control-feed", type=str, help="Source of control feed") parser.add_argument( "--control-feed-expiry", type=int, default=86400, help="Maximum age of the control feed (in seconds, default: 86400)" ) parser.add_argument("--order-history", type=str, help="Endpoint to report active orders to") parser.add_argument( "--order-history-every", type=int, default=30, help= "Frequency of reporting active orders (in seconds, default: 30)") parser.add_argument( "--round-places", type=int, default=2, help="Number of decimal places to round order prices to (default=2)" ) parser.add_argument( "--min-eth-balance", type=float, default=0, help="Minimum ETH balance below which keeper will cease operation") parser.add_argument("--gas-price", type=int, default=0, help="Gas price (in Wei)") parser.add_argument( "--smart-gas-price", dest='smart_gas_price', action='store_true', help= "Use smart gas pricing strategy, based on the ethgasstation.info feed" ) parser.add_argument("--ethgasstation-api-key", type=str, default=None, help="ethgasstation API key") parser.add_argument( "--refresh-frequency", type=int, default=10, help="Order book refresh frequency (in seconds, default: 10)") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) setup_logging(self.arguments) if 'web3' in kwargs: self.web3 = kwargs['web3'] elif self.arguments.endpoint_uri: self.web3: Web3 = web3_via_http(self.arguments.endpoint_uri, self.arguments.rpc_timeout) else: self.logger.warning( "Configuring node endpoint by host and port is deprecated; please use --endpoint-uri" ) self.web3 = Web3( HTTPProvider( endpoint_uri= f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}", request_kwargs={"timeout": self.arguments.rpc_timeout})) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) self.otc = MatchingMarket( web3=self.web3, address=Address(self.arguments.oasis_address), support_address=Address(self.arguments.oasis_support_address) if self.arguments.oasis_support_address else None) tub = Tub(web3=self.web3, address=Address(self.arguments.tub_address)) \ if self.arguments.tub_address is not None else None self.token_buy = ERC20Token(web3=self.web3, address=Address( self.arguments.buy_token_address)) self.token_sell = ERC20Token(web3=self.web3, address=Address( self.arguments.sell_token_address)) self.buy_token = Token(name=self.arguments.buy_token_name, address=Address( self.arguments.buy_token_address), decimals=self.arguments.buy_token_decimals) self.sell_token = Token(name=self.arguments.sell_token_name, address=Address( self.arguments.sell_token_address), decimals=self.arguments.sell_token_decimals) self.min_eth_balance = Wad.from_number(self.arguments.min_eth_balance) self.bands_config = ReloadableConfig(self.arguments.config) self.gas_price = GasPriceFactory().create_gas_price( self.web3, self.arguments) self.price_feed = PriceFeedFactory().create_price_feed( self.arguments, tub) self.spread_feed = create_spread_feed(self.arguments) self.control_feed = create_control_feed(self.arguments) self.order_history_reporter = create_order_history_reporter( self.arguments) self.history = History() self.order_book_manager = OrderBookManager( refresh_frequency=self.arguments.refresh_frequency) self.order_book_manager.get_orders_with(lambda: self.our_orders()) self.order_book_manager.place_orders_with(self.place_order_function) self.order_book_manager.cancel_orders_with(self.cancel_order_function) self.order_book_manager.enable_history_reporting( self.order_history_reporter, self.our_buy_orders, self.our_sell_orders) self.order_book_manager.start()
def __init__(self, args, **kwargs): """Pass in arguements assign necessary variables/objects and instantiate other Classes""" parser = argparse.ArgumentParser("simple-arbitrage-keeper") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost:8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help= "Ethereum address from which to send transactions; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--eth-key", type=str, nargs='*', required=True, help= "Ethereum private key(s) to use (e.g. 'key_file=/path/to/keystore.json,pass_file=/path/to/passphrase.txt')" ) parser.add_argument( "--uniswap-entry-exchange", type=str, required=True, help= "Ethereum address of the Uniswap Exchange contract for the entry token market; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--uniswap-arb-exchange", type=str, required=True, help= "Ethereum address of the Uniswap Exchange contract for the arb token market; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--oasis-address", type=str, required=True, help= "Ethereum address of the OasisDEX contract; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--oasis-api-endpoint", type=str, required=True, help= "Endpoint of of the Oasis V2 REST API (e.g. 'https://kovan-api.oasisdex.com' )" ) parser.add_argument( "--relayer-per-page", type=int, default=100, help= "Number of orders to fetch per one page from the 0x Relayer API (default: 100)" ) parser.add_argument( "--tx-manager", type=str, required=True, help= "Ethereum address of the TxManager contract to use for multi-step arbitrage; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--gas-price", type=int, default=0, help= "Gas price in Wei (default: node default), (e.g. 1000000000 for 1 GWei)" ) parser.add_argument( "--entry-token", type=str, required=True, help= "The token address that the bot starts and ends with in every transaction; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--arb-token", type=str, required=True, help= "The token address that arbitraged between both exchanges; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--arb-token-name", type=str, required=True, help= "The token name that arbitraged between both exchanges (e.g. 'SAI', 'WETH', 'REP')" ) parser.add_argument( "--min-profit", type=int, required=True, help= "Ether amount of minimum profit (in base token) from one arbitrage operation (e.g. 1 for 1 Sai min profit)" ) parser.add_argument( "--max-engagement", type=int, required=True, help= "Ether amount of maximum engagement (in base token) in one arbitrage operation (e.g. 100 for 100 Sai max engagement)" ) parser.add_argument( "--max-errors", type=int, default=100, help= "Maximum number of allowed errors before the keeper terminates (default: 100)" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else web3_via_http( endpoint_uri=self.arguments.rpc_host, timeout=self.arguments.rpc_timeout) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # self.sai = ERC20Token(web3=self.web3, address=Address('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359')) # Mainnet Sai # self.dai = ERC20Token(web3=self.web3, address=Address('0x6b175474e89094c44da98b954eedeac495271d0f')) # Mainnet Dai self.ksai = ERC20Token( web3=self.web3, address=Address( '0xC4375B7De8af5a38a93548eb8453a498222C4fF2')) #Kovan Sai self.kdai = ERC20Token( web3=self.web3, address=Address( '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa')) #Kovan Dai self.entry_token = ERC20Token(web3=self.web3, address=Address( self.arguments.entry_token)) self.arb_token = ERC20Token(web3=self.web3, address=Address(self.arguments.arb_token)) self.arb_token.name = self.arguments.arb_token_name \ if self.arguments.arb_token_name != 'WETH' else 'ETH' self.uniswap_entry_exchange = UniswapWrapper(self.web3, self.entry_token.address, Address(self.arguments.uniswap_entry_exchange)) \ if self.arguments.uniswap_entry_exchange is not None else None self.uniswap_arb_exchange = UniswapWrapper(self.web3, self.arb_token.address, Address(self.arguments.uniswap_arb_exchange)) \ if self.arguments.uniswap_arb_exchange is not None else None self.oasis_api_endpoint = OasisAPI(api_server=self.arguments.oasis_api_endpoint, entry_token_name=self.token_name(self.entry_token.address), arb_token_name=self.arb_token.name) \ if self.arguments.oasis_api_endpoint is not None else None self.oasis = MatchingMarket(web3=self.web3, address=Address( self.arguments.oasis_address)) self.min_profit = Wad(int(self.arguments.min_profit * 10**18)) self.max_engagement = Wad(int(self.arguments.max_engagement * 10**18)) self.max_errors = self.arguments.max_errors self.errors = 0 if self.arguments.tx_manager: self.tx_manager = TxManager(web3=self.web3, address=Address( self.arguments.tx_manager)) if self.tx_manager.owner() != self.our_address: raise Exception( f"The TxManager has to be owned by the address the keeper is operating from." ) else: self.tx_manager = None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO))