def test_comparison(self, deployment: Deployment): # expect assert deployment.tap == deployment.tap assert deployment.tap == Tap(web3=deployment.web3, address=deployment.tap.address) assert deployment.tap != Tap(web3=deployment.web3, address=deployment.tub.address)
def __init__(self, web3, dai_tub = '0x448a5065aeBB8E423F0896E6c5D525C040f59af3'): self.web3 = web3 self.tub = Tub(web3=web3, address=Address(dai_tub)) self.tap = Tap(web3=web3, address=self.tub.tap()) self.tokens = { 'MKR': ERC20Token(web3, self.tub.gov()), 'PETH': ERC20Token(web3, self.tub.skr()), 'WETH': ERC20Token(web3, self.tub.gem()), 'DAI': ERC20Token(web3, self.tub.sai()), }
def bustable_amount_in_sai(self, tap: Tap): #TODO we always try to bust 10 SAI less than what the Tub reports #in order to discount the growth of `joy()` that might've have happened since the last drip #of course this is not the right solution and it won't even work properly if the last #drip happened enough time ago bustable_woe = tap.woe() - tap.joy() - Wad.from_number(10) # we deduct 0.000001 in order to avoid rounding errors bustable_fog = tap.fog() * tap.ask( Wad.from_number(1)) - Wad.from_number(0.000001) return Wad.max(bustable_woe, bustable_fog, Wad.from_number(0))
def boomable_amount_in_skr(self, tap: Tap): # we deduct 0.000001 in order to avoid rounding errors return Wad.max( Wad( self.boomable_amount_in_sai(tap) / (tap.bid(Wad.from_number(1)))) - Wad.from_number(0.000001), Wad.from_number(0))
def __init__(self, tub: Tub, tap: Tap): self.tub = tub self.tap = tap super().__init__(source_token=self.tub.skr(), target_token=self.tub.sai(), rate=Ray(tap.bid(Wad.from_number(1))), max_source_amount=self.boomable_amount_in_skr(tap), method="tub.boom()")
def __init__(self, tub: Tub, tap: Tap): self.tub = tub self.tap = tap super().__init__(source_token=self.tub.sai(), target_token=self.tub.skr(), rate=(Ray.from_number(1) / Ray(tap.ask(Wad.from_number(1)))), max_source_amount=self.bustable_amount_in_sai(tap), method="tub.bust()")
def __init__(self): web3 = Web3(HTTPProvider("http://localhost:8555")) web3.eth.defaultAccount = web3.eth.accounts[0] our_address = Address(web3.eth.defaultAccount) sai = DSToken.deploy(web3, 'DAI') sin = DSToken.deploy(web3, 'SIN') skr = DSToken.deploy(web3, 'PETH') gem = DSToken.deploy(web3, 'WETH') gov = DSToken.deploy(web3, 'MKR') pip = DSValue.deploy(web3) pep = DSValue.deploy(web3) pit = DSVault.deploy(web3) vox = Vox.deploy(web3, per=Ray.from_number(1)) tub = Tub.deploy(web3, sai=sai.address, sin=sin.address, skr=skr.address, gem=gem.address, gov=gov.address, pip=pip.address, pep=pep.address, vox=vox.address, pit=pit.address) tap = Tap.deploy(web3, tub.address) top = Top.deploy(web3, tub.address, tap.address) tub._contract.functions.turn(tap.address.address).transact() etherdelta = EtherDelta.deploy( web3, admin=Address('0x1111100000999998888877777666665555544444'), fee_account=Address('0x8888877777666665555544444111110000099999'), account_levels_addr=Address( '0x0000000000000000000000000000000000000000'), fee_make=Wad.from_number(0.01), fee_take=Wad.from_number(0.02), fee_rebate=Wad.from_number(0.03)) # set permissions dad = DSGuard.deploy(web3) dad.permit(DSGuard.ANY, DSGuard.ANY, DSGuard.ANY).transact() tub.set_authority(dad.address).transact() for auth in [sai, sin, skr, gem, gov, pit, tap, top]: auth.set_authority(dad.address).transact() # approve tub.approve(directly()) tap.approve(directly()) # mint some GEMs gem.mint(Wad.from_number(1000000)).transact() self.snapshot_id = web3.manager.request_blocking("evm_snapshot", []) self.web3 = web3 self.our_address = our_address self.sai = sai self.sin = sin self.skr = skr self.gem = gem self.gov = gov self.vox = vox self.tub = tub self.tap = tap self.top = top self.etherdelta = etherdelta
def __init__(self): web3 = Web3(EthereumTesterProvider()) web3.eth.defaultAccount = web3.eth.accounts[0] our_address = Address(web3.eth.defaultAccount) sai = DSToken.deploy(web3, 'DAI') sin = DSToken.deploy(web3, 'SIN') skr = DSToken.deploy(web3, 'PETH') gem = DSToken.deploy(web3, 'WETH') gov = DSToken.deploy(web3, 'MKR') pip = DSValue.deploy(web3) pep = DSValue.deploy(web3) pit = DSVault.deploy(web3) vox = Vox.deploy(web3, per=Ray.from_number(1)) tub = Tub.deploy(web3, sai=sai.address, sin=sin.address, skr=skr.address, gem=gem.address, gov=gov.address, pip=pip.address, pep=pep.address, vox=vox.address, pit=pit.address) tap = Tap.deploy(web3, tub.address) top = Top.deploy(web3, tub.address, tap.address) tub._contract.transact().turn(tap.address.address) otc = MatchingMarket.deploy(web3, 2600000000) etherdelta = EtherDelta.deploy(web3, admin=Address('0x1111100000999998888877777666665555544444'), fee_account=Address('0x8888877777666665555544444111110000099999'), account_levels_addr=Address('0x0000000000000000000000000000000000000000'), fee_make=Wad.from_number(0.01), fee_take=Wad.from_number(0.02), fee_rebate=Wad.from_number(0.03)) # set permissions dad = DSGuard.deploy(web3) dad.permit(DSGuard.ANY, DSGuard.ANY, DSGuard.ANY).transact() tub.set_authority(dad.address).transact() for auth in [sai, sin, skr, gem, gov, pit, tap, top]: auth.set_authority(dad.address).transact() # whitelist pairs otc.add_token_pair_whitelist(sai.address, gem.address).transact() # approve tub.approve(directly()) tap.approve(directly()) # mint some GEMs gem.mint(Wad.from_number(1000000)).transact() web3.providers[0].rpc_methods.evm_snapshot() self.web3 = web3 self.our_address = our_address self.sai = sai self.sin = sin self.skr = skr self.gem = gem self.gov = gov self.vox = vox self.tub = tub self.tap = tap self.top = top self.otc = otc self.etherdelta = etherdelta
def test_fail_when_no_contract_under_that_address(self, deployment: Deployment): # expect with pytest.raises(Exception): Tap(web3=deployment.web3, address=Address('0xdeadadd1e5500000000000000000000000000000'))
def __init__(self, args, **kwargs): parser = argparse.ArgumentParser("arbitrage-keeper") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="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=True, help="Ethereum address of the Tub contract") parser.add_argument("--tap-address", type=str, required=True, help="Ethereum address of the Tap contract") parser.add_argument( "--exchange-address", type=str, help="Ethereum address of the 0x Exchange 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("--relayer-api-server", type=str, help="Address of the 0x Relayer API") 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, help= "Ethereum address of the TxManager contract to use for multi-step arbitrage" ) parser.add_argument("--gas-price", type=int, default=0, help="Gas price in Wei (default: node default)") parser.add_argument( "--base-token", type=str, required=True, help="The token all arbitrage sequences will start and end with") parser.add_argument( "--min-profit", type=float, required=True, help="Minimum profit (in base token) from one arbitrage operation") parser.add_argument( "--max-engagement", type=float, required=True, help="Maximum engagement (in base token) in one arbitrage operation" ) 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 = kwargs['web3'] if 'web3' in kwargs else 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.tub = Tub(web3=self.web3, address=Address(self.arguments.tub_address)) self.tap = Tap(web3=self.web3, address=Address(self.arguments.tap_address)) self.gem = ERC20Token(web3=self.web3, address=self.tub.gem()) self.sai = ERC20Token(web3=self.web3, address=self.tub.sai()) self.skr = ERC20Token(web3=self.web3, address=self.tub.skr()) self.zrx_exchange = ZrxExchange(web3=self.web3, address=Address(self.arguments.exchange_address)) \ if self.arguments.exchange_address is not None else None self.zrx_relayer_api = ZrxRelayerApi(exchange=self.zrx_exchange, api_server=self.arguments.relayer_api_server) \ if self.arguments.relayer_api_server is not None else None 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 is not None else None) self.base_token = ERC20Token(web3=self.web3, address=Address( self.arguments.base_token)) self.min_profit = Wad.from_number(self.arguments.min_profit) self.max_engagement = Wad.from_number(self.arguments.max_engagement) 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))
class ArbitrageKeeper: """Keeper to arbitrage on OasisDEX, `join`, `exit`, `boom` and `bust`.""" logger = logging.getLogger('arbitrage-keeper') def __init__(self, args, **kwargs): parser = argparse.ArgumentParser("arbitrage-keeper") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="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=True, help="Ethereum address of the Tub contract") parser.add_argument("--tap-address", type=str, required=True, help="Ethereum address of the Tap contract") parser.add_argument( "--exchange-address", type=str, help="Ethereum address of the 0x Exchange 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("--relayer-api-server", type=str, help="Address of the 0x Relayer API") 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, help= "Ethereum address of the TxManager contract to use for multi-step arbitrage" ) parser.add_argument("--gas-price", type=int, default=0, help="Gas price in Wei (default: node default)") parser.add_argument( "--base-token", type=str, required=True, help="The token all arbitrage sequences will start and end with") parser.add_argument( "--min-profit", type=float, required=True, help="Minimum profit (in base token) from one arbitrage operation") parser.add_argument( "--max-engagement", type=float, required=True, help="Maximum engagement (in base token) in one arbitrage operation" ) 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 = kwargs['web3'] if 'web3' in kwargs else 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.tub = Tub(web3=self.web3, address=Address(self.arguments.tub_address)) self.tap = Tap(web3=self.web3, address=Address(self.arguments.tap_address)) self.gem = ERC20Token(web3=self.web3, address=self.tub.gem()) self.sai = ERC20Token(web3=self.web3, address=self.tub.sai()) self.skr = ERC20Token(web3=self.web3, address=self.tub.skr()) self.zrx_exchange = ZrxExchange(web3=self.web3, address=Address(self.arguments.exchange_address)) \ if self.arguments.exchange_address is not None else None self.zrx_relayer_api = ZrxRelayerApi(exchange=self.zrx_exchange, api_server=self.arguments.relayer_api_server) \ if self.arguments.relayer_api_server is not None else None 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 is not None else None) self.base_token = ERC20Token(web3=self.web3, address=Address( self.arguments.base_token)) self.min_profit = Wad.from_number(self.arguments.min_profit) self.max_engagement = Wad.from_number(self.arguments.max_engagement) 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)) def main(self): with Lifecycle(self.web3) as lifecycle: self.lifecycle = lifecycle lifecycle.on_startup(self.startup) lifecycle.on_block(self.process_block) def startup(self): self.approve() def approve(self): """Approve all components that need to access our balances""" approval_method = via_tx_manager(self.tx_manager, gas_price=self.gas_price()) if self.tx_manager \ else directly(gas_price=self.gas_price()) self.tub.approve(approval_method) self.tap.approve(approval_method) self.otc.approve([self.gem, self.sai, self.skr], approval_method) if self.zrx_exchange: self.zrx_exchange.approve([self.gem, self.sai], approval_method) if self.tx_manager: self.tx_manager.approve([self.gem, self.sai, self.skr], directly(gas_price=self.gas_price())) def token_name(self, address: Address) -> str: if address == self.sai.address: return "DAI" elif address == self.gem.address: return "WETH" elif address == self.skr.address: return "PETH" else: return str(address) def tub_conversions(self) -> List[Conversion]: return [ TubJoinConversion(self.tub), TubExitConversion(self.tub), TubBoomConversion(self.tub, self.tap), TubBustConversion(self.tub, self.tap) ] def otc_orders(self, tokens): orders = [] for token1 in tokens: for token2 in tokens: if token1 != token2: orders = orders + self.otc.get_orders(token1, token2) return orders def otc_conversions(self, tokens) -> List[Conversion]: return list( map(lambda order: OasisTakeConversion(self.otc, order), self.otc_orders(tokens))) def zrx_orders(self, tokens): if self.zrx_exchange is None or self.zrx_relayer_api is None: return [] orders = [] for token1 in tokens: for token2 in tokens: if token1 != token2: orders = orders + self.zrx_relayer_api.get_orders( token1, token2) return list( filter(lambda order: order.expiration <= time.time(), orders)) def zrx_conversions(self, tokens) -> List[Conversion]: return list( map(lambda order: ZrxFillOrderConversion(self.zrx_exchange, order), self.zrx_orders(tokens))) def all_conversions(self): return self.tub_conversions() + \ self.otc_conversions([self.sai.address, self.skr.address, self.gem.address]) + \ self.zrx_conversions([self.sai.address, self.gem.address]) def process_block(self): """Callback called on each new block. If too many errors, terminate the keeper to minimize potential damage.""" if self.errors >= self.max_errors: self.lifecycle.terminate() else: self.execute_best_opportunity_available() def execute_best_opportunity_available(self): """Find the best arbitrage opportunity present and execute it.""" opportunity = self.best_opportunity(self.profitable_opportunities()) if opportunity: self.print_opportunity(opportunity) self.execute_opportunity(opportunity) def profitable_opportunities(self): """Identify all profitable arbitrage opportunities within given limits.""" entry_amount = Wad.min(self.base_token.balance_of(self.our_address), self.max_engagement) opportunity_finder = OpportunityFinder( conversions=self.all_conversions()) opportunities = opportunity_finder.find_opportunities( self.base_token.address, entry_amount) opportunities = filter( lambda op: op.total_rate() > Ray.from_number(1.000001), opportunities) opportunities = filter( lambda op: op.profit(self.base_token.address) > self.min_profit, opportunities) opportunities = sorted( opportunities, key=lambda op: op.profit(self.base_token.address), reverse=True) return opportunities def best_opportunity(self, opportunities: List[Sequence]): """Pick the best opportunity, or return None if no profitable opportunities.""" return opportunities[0] if len(opportunities) > 0 else None def print_opportunity(self, opportunity: Sequence): """Print the details of the opportunity.""" self.logger.info( f"Opportunity with id={opportunity.id()}," f" profit={opportunity.profit(self.base_token.address)} {self.token_name(self.base_token.address)}" ) for index, conversion in enumerate(opportunity.steps, start=1): self.logger.info( f"Step {index}/{len(opportunity.steps)}: {conversion.name()}" f" (from {conversion.source_amount} {self.token_name(conversion.source_token)}" f" to {conversion.target_amount} {self.token_name(conversion.target_token)})" ) def execute_opportunity(self, opportunity: Sequence): """Execute the opportunity either in one Ethereum transaction or step-by-step. Depending on whether `tx_manager` is available.""" if self.tx_manager: self.execute_opportunity_in_one_transaction(opportunity) else: self.execute_opportunity_step_by_step(opportunity) def execute_opportunity_step_by_step(self, opportunity: Sequence): """Execute the opportunity step-by-step.""" def incoming_transfer(our_address: Address): return lambda transfer: transfer.to_address == our_address def outgoing_transfer(our_address: Address): return lambda transfer: transfer.from_address == our_address all_transfers = [] for step in opportunity.steps: receipt = step.transact().transact(gas_price=self.gas_price()) if receipt: all_transfers += receipt.transfers outgoing = TransferFormatter().format( filter(outgoing_transfer(self.our_address), receipt.transfers), self.token_name) incoming = TransferFormatter().format( filter(incoming_transfer(self.our_address), receipt.transfers), self.token_name) self.logger.info(f"Exchanged {outgoing} to {incoming}") else: self.errors += 1 return self.logger.info( f"The profit we made is {TransferFormatter().format_net(all_transfers, self.our_address, self.token_name)}" ) def execute_opportunity_in_one_transaction(self, opportunity: Sequence): """Execute the opportunity in one transaction, using the `tx_manager`.""" tokens = [self.sai.address, self.skr.address, self.gem.address] invocations = list( map(lambda step: step.transact().invocation(), opportunity.steps)) receipt = self.tx_manager.execute( tokens, invocations).transact(gas_price=self.gas_price()) if receipt: self.logger.info( f"The profit we made is {TransferFormatter().format_net(receipt.transfers, self.our_address, self.token_name)}" ) else: self.errors += 1 def gas_price(self): if self.arguments.gas_price > 0: return FixedGasPrice(self.arguments.gas_price) else: return DefaultGasPrice()
def boomable_amount_in_sai(self, tap: Tap): return Wad.max(tap.joy() - tap.woe(), Wad.from_number(0))
class DAIv1(Market): def __init__(self, web3, dai_tub = '0x448a5065aeBB8E423F0896E6c5D525C040f59af3'): self.web3 = web3 self.tub = Tub(web3=web3, address=Address(dai_tub)) self.tap = Tap(web3=web3, address=self.tub.tap()) self.tokens = { 'MKR': ERC20Token(web3, self.tub.gov()), 'PETH': ERC20Token(web3, self.tub.skr()), 'WETH': ERC20Token(web3, self.tub.gem()), 'DAI': ERC20Token(web3, self.tub.sai()), } def get_cup(self, cup_id): cup = self.tub.cups(cup_id) return { 'id': cup.cup_id, 'lad': cup.lad.address, 'art': float(cup.art), 'ink': float(cup.ink), 'safe': self.tub.safe(cup_id) } def get_cups(self): last_cup_id = self.tub.cupi() cups = map(self.get_cup, range(1, last_cup_id+1)) not_empty_cups = filter(lambda cup: cup['lad'] != "0x0000000000000000000000000000000000000000", cups) return list(not_empty_cups) def get_pairs(self): pairs = ['PETH/DAI', 'PETH/WETH'] return pairs def get_orders(self, base, quote): depth = {'bids': [], 'asks': []} # PETH/DAI order book if base == 'PETH' and quote == 'DAI': # boom is a taker using a bid side from maker tap # a taker convert PETH to DAI using tap.bid(1) as price # maker side offer DAI in exchange for PETH (flap) # DAI qty offered by is min(joy - woe, 0) order = { 'price': float(self.tap.bid(Wad.from_number(1))), 'amount': float(min(self.tap.joy() - self.tap.woe(), Wad.from_number(0))), 'id': 'take:tap.boom()', } if order['amount'] > 0: depth['bids'].append(order) # bust is a taker using ask side from maker tap # a taker convert DAI to PETH using tap.ask(1) as price # maker side offer PETH from fog (flip) and PETH minted to cover woe (flop) # PETH qty offered by maker is fog+min(woe-joy, 0)/ask order = { 'price': float(self.tap.ask(Wad.from_number(1))), 'amount': float(self.tap.fog() + min(self.tap.woe() - self.tap.joy(), Wad.from_number(0)) / self.tap.ask(Wad.from_number(1))), 'id': 'take:tap.bust()', } if order['amount'] > 0: depth['asks'].append(order) # PETH/WETH order book if base == 'PETH' and quote == 'WETH': # exit is a taker using a bid side from maker tub # a taker PETH to WETH using tub.bid(1) as price # maker side offer WETH in exchange for PETH # WETH qty offered by maker is infinity (2**32 as a large number for infinity ...) order = { 'price': float(self.tub.bid(Wad.from_number(1))), 'amount': float(2**32), 'id': 'take:tub.exit()', } depth['bids'].append(order) # join is a taker using ask side from maker tub # a taker convert WETH to PETH usgin tub.ask(1) as price # maker side offer PETH in exchange for WETH # PETH qty offered by maker is infinity (2**32 as a large number for infinity ...) order = { 'price': float(self.tub.ask(Wad.from_number(1))), 'amount': float(2**32), 'id': 'take:tub.join()', } depth['asks'].append(order) return depth def get_accounts(self, manager_url): accounts = {} for addr in requests.get(manager_url).json(): accounts[addr] = { 'balance' : self.web3.eth.getBalance(addr), } accounts[addr]['tokens'] = {} for name, token in self.tokens.items(): balance = token.balance_of(Address(addr)) allowance = token.allowance_of(Address(addr), self.tub.address) #TODO check tap allowance ... if float(allowance) or float(balance): accounts[addr]['tokens'][name] = { 'allowance': float(allowance), 'balance': float(balance), } return accounts
def __init__(self, args, **kwargs): parser = argparse.ArgumentParser("arbitrage-keeper") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="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("--tub-address", type=str, required=True, help="Ethereum address of the Tub contract") parser.add_argument("--tap-address", type=str, required=True, help="Ethereum address of the Tap contract") parser.add_argument("--oasis-address", type=str, required=True, help="Ethereum address of the OasisDEX contract") parser.add_argument("--tx-manager", type=str, help="Ethereum address of the TxManager contract to use for multi-step arbitrage") parser.add_argument("--gas-price", type=int, default=0, help="Gas price in Wei (default: node default)") parser.add_argument("--base-token", type=str, required=True, help="The token all arbitrage sequences will start and end with") parser.add_argument("--min-profit", type=float, required=True, help="Minimum profit (in base token) from one arbitrage operation") parser.add_argument("--max-engagement", type=float, required=True, help="Maximum engagement (in base token) in one arbitrage operation") 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("--ipcpath", type=str, required=True, help="Local IPC Path") parser.add_argument("--eth-from-password", type=str, required=True, help="Eth account password") self.arguments = parser.parse_args(args) self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(IPCProvider(ipc_path=self.arguments.ipcpath)) self.web3.eth.defaultAccount = self.arguments.eth_from if !self.web3.personal.unlockAccount(self.web3.eth.defaultAccount, self.arguments.eth_from_password): raise Exception(f"Incorrect account password") self.our_address = Address(self.arguments.eth_from) self.otc = MatchingMarket(web3=self.web3, address=Address(self.arguments.oasis_address)) self.tub = Tub(web3=self.web3, address=Address(self.arguments.tub_address)) self.tap = Tap(web3=self.web3, address=Address(self.arguments.tap_address)) self.gem = ERC20Token(web3=self.web3, address=self.tub.gem()) self.sai = ERC20Token(web3=self.web3, address=self.tub.sai()) self.skr = ERC20Token(web3=self.web3, address=self.tub.skr()) self.base_token = ERC20Token(web3=self.web3, address=Address(self.arguments.base_token)) self.min_profit = Wad.from_number(self.arguments.min_profit) self.max_engagement = Wad.from_number(self.arguments.max_engagement) 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))