price constraints working

This commit is contained in:
Tim Olson
2023-11-05 16:49:42 -04:00
parent 6af695d345
commit 73bb0fd635
17 changed files with 224 additions and 180 deletions

View File

@@ -44,6 +44,6 @@ Polygon = Blockchain(137, 'Polygon') # POS not zkEVM
Mumbai = Blockchain(80001, 'Mumbai')
BSC = Blockchain(56, 'BSC')
Arbitrum = Blockchain(42161, 'Arbitrum', 3, batch_size=1000) # todo configure batch size... does it depend on log count? :(
Mock = Blockchain(31337, 'Mock', 3)
Mock = Blockchain(31337, 'Mock', 3, batch_size=10000)
current_chain = ContextVar[Blockchain]('current_chain')

View File

@@ -119,4 +119,3 @@ for _chain_id, _native in _native_tokens.items():
_tokens_by_chain[_chain_id][_native.symbol] = _native
tokens = ByBlockchainDict[Token](_tokens_by_chain)

View File

@@ -2,8 +2,7 @@ import logging
import sys
from asyncio import CancelledError
from dexorder import db, config, Blockchain, blockchain
from dexorder.base.chain import current_chain
from dexorder import db, blockchain
from dexorder.bin.executable import execute
from dexorder.blockstate.blockdata import BlockData
from dexorder.blockstate.db_state import DbState

View File

@@ -1,40 +0,0 @@
from charset_normalizer.md import getLogger
from eth_abi.packed import encode_packed
from eth_utils import keccak, to_bytes, to_checksum_address
from dexorder import dec
from dexorder.contract import abi_encoder
from dexorder.util import hexbytes
UNISWAPV3_POOL_INIT_CODE_HASH = hexbytes('0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54')
log = getLogger(__name__)
class Fee:
LOWEST = 100
LOW = 500
MEDIUM = 3000
HIGH = 10000
def ordered_addresses(addr_a:str, addr_b:str):
return (addr_a, addr_b) if addr_a.lower() <= addr_b.lower() else (addr_b, addr_a)
def uniswap_pool_address(factory_addr: str, addr_a: str, addr_b: str, fee: int) -> str:
token0, token1 = ordered_addresses(addr_a, addr_b)
salt = keccak(abi_encoder.encode(['address','address','uint24'],[token0, token1, fee]))
contract_address = keccak(
b"\xff"
+ to_bytes(hexstr=factory_addr)
+ salt
+ UNISWAPV3_POOL_INIT_CODE_HASH
).hex()[-40:]
result = to_checksum_address(contract_address)
# log.debug(f'uniswap pool address {factory_addr} {addr_a} {addr_b} {fee} => {result}')
return result
def uniswap_price(sqrt_price):
d = dec(sqrt_price)
price = d * d / dec(2 ** (96 * 2))
return price

View File

@@ -4,15 +4,12 @@ from eth_abi.codec import ABIDecoder, ABIEncoder
from eth_abi.registry import registry as default_registry
from .. import current_w3 as _current_w3
from .abi import abis
from .contract_proxy import ContractProxy
abi_decoder = ABIDecoder(default_registry)
abi_encoder = ABIEncoder(default_registry)
from .abi import abis
from .contract_proxy import ContractProxy
from .pool_contract import UniswapV3Pool
from .uniswap_contracts import uniswapV3
def get_contract_data(name):
with open(f'../contract/out/{name}.sol/{name}.json', 'rt') as file:

View File

@@ -2,13 +2,11 @@ import json
from typing import Optional
import eth_account
from eth_utils import keccak
from web3.types import TxReceipt
from dexorder import current_w3, Account
from dexorder import current_w3
from dexorder.base.account import current_account
from dexorder.database.model.block import current_block
from dexorder.base import TransactionDict
from dexorder.util import hexstr

View File

@@ -0,0 +1,16 @@
import logging
from dexorder import db
from dexorder.contract import ContractProxy
log = logging.getLogger(__name__)
async def token_decimals(addr):
key = f'td|{addr}'
try:
return db.kv[key]
except KeyError:
decimals = await ContractProxy(addr, 'ERC20').decimals()
db.kv[key] = decimals
return decimals

View File

@@ -1,10 +0,0 @@
from .contract_proxy import ContractProxy
from ..blockchain.uniswap import uniswap_price
class UniswapV3Pool (ContractProxy):
def __init__(self, address: str = None):
super().__init__(address, 'IUniswapV3Pool')
async def price(self):
return uniswap_price((await self.slot0())[0])

View File

@@ -1,23 +0,0 @@
from dexorder.base.chain import Ethereum, Goerli, Polygon, Mumbai, Arbitrum, Mock
from dexorder.blockchain.uniswap import uniswap_pool_address
from dexorder.contract.contract_proxy import ContractProxy
from dexorder.blockchain import ByBlockchainDict
class _UniswapContracts (ByBlockchainDict[ContractProxy]):
def __init__(self):
std = {
'factory': ContractProxy('0x1F98431c8aD98523631AE4a59f267346ea31F984', 'IUniswapV3Factory'),
'nfpm': ContractProxy('0xC36442b4a4522E871399CD717aBDD847Ab11FE88', 'INonfungiblePositionManager'),
'quoter': ContractProxy('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', 'IQuoter'),
'swap_router': ContractProxy('0xE592427A0AEce92De3Edee1F18E0157C05861564', 'ISwapRouter'),
}
super().__init__({chain.chain_id:std for chain in (Ethereum, Polygon, Goerli, Mumbai, Arbitrum, Mock)})
uniswapV3 = _UniswapContracts()
def uniswapV3_pool_address( addr_a: str, addr_b: str, fee: int):
return uniswap_pool_address(uniswapV3['factory'].address, addr_a, addr_b, fee)

View File

@@ -1,6 +1,7 @@
from dexorder import dec
from dexorder.base.chain import current_chain
from dexorder.blockstate import BlockDict
from dexorder.blockstate.blockdata import K, V
from dexorder.util import json, defaultdictk
# pub=... publishes to a channel for web clients to consume. argument is (key,value) and return must be (event,room,args)
@@ -14,5 +15,15 @@ vault_balances: BlockDict[str, dict[str, int]] = BlockDict(
str2value=lambda s: {k: int(v) for k, v in json.loads(s).items()},
pub=lambda k, v: (f'{current_chain.get().chain_id}|{vault_owners[k]}', 'vb', (k,json.dumps({k2: str(v2) for k2, v2 in v.items()})))
)
pool_prices: BlockDict[str, dec] = BlockDict('p', db=True, redis=True, value2str=lambda d: f'{d:f}', str2value=dec,
pub=lambda k, v: (f'{current_chain.get().chain_id}|{k}', 'p', (k, str(v))))
class PoolPrices (BlockDict[str, dec]):
def __setitem__(self, item: K, value: V) -> None:
super().__setitem__(item, value)
new_pool_prices[item] = value
new_pool_prices: dict[str, dec] = {} # tracks which prices were set during the current block. cleared every block.
pool_prices: PoolPrices = PoolPrices('p', db=True, redis=True,
value2str=lambda d: f'{d:f}', str2value=dec,
pub=lambda k, v: (f'{current_chain.get().chain_id}|{k}', 'p', (k, str(v))))

View File

@@ -1,10 +1,7 @@
import logging
from typing import Union
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import mapped_column, Mapped
from dexorder.database.column import Json
from dexorder.database.model import Base
log = logging.getLogger(__name__)

View File

@@ -4,11 +4,9 @@ from typing import Optional
import sqlalchemy as sa
from sqlalchemy import ForeignKey
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import mapped_column, Mapped, relationship
from dexorder.base.order import TransactionRequest as TransactionRequestDict, deserialize_transaction_request
from dexorder.base import TransactionDict
from dexorder.database.column import Dict, Bytes, UUID_PK, Blockchain, UUID
from dexorder.database.column_types import DataclassDict
from dexorder.database.model import Base

View File

@@ -3,20 +3,21 @@ from uuid import UUID
from web3.types import EventData
from dexorder import current_pub, db
from dexorder import current_pub, db, dec
from dexorder.base.chain import current_chain
from dexorder.base.order import TrancheExecutionRequest, TrancheKey, ExecutionRequest, new_tranche_execution_request
from dexorder.transaction import handle_create_transactions, submit_transaction_request, handle_transaction_receipts, handle_send_transactions
from dexorder.blockchain.uniswap import uniswap_price
from dexorder.transaction import create_transactions, submit_transaction_request, handle_transaction_receipts, send_transactions
from dexorder.uniswap import UniswapV3Pool, uniswap_price
from dexorder.contract.dexorder import get_factory_contract, vault_address, VaultContract, get_dexorder_contract
from dexorder.contract import UniswapV3Pool, get_contract_event
from dexorder.data import pool_prices, vault_owners, vault_balances
from dexorder.contract import get_contract_event
from dexorder.data import pool_prices, vault_owners, vault_balances, new_pool_prices
from dexorder.database.model.block import current_block
from dexorder.database.model.transaction import TransactionJob
from dexorder.order.orderlib import SwapOrderState, SwapOrderStatus
from dexorder.order.orderstate import Order
from dexorder.order.triggers import OrderTriggers, close_order_and_disable_triggers, price_triggers, time_triggers, \
unconstrained_price_triggers, execution_requests, inflight_execution_requests, TrancheStatus
unconstrained_price_triggers, execution_requests, inflight_execution_requests, TrancheStatus, active_tranches, new_price_triggers
from dexorder.util.async_util import maywait
log = logging.getLogger(__name__)
@@ -60,6 +61,7 @@ def setup_logevent_triggers(runner):
# THIS IS BASICALLY THE MAIN RUN LOOP FOR EVERY BLOCK
#
runner.add_event_trigger(init)
runner.add_event_trigger(handle_vault_created, vault_created)
runner.add_event_trigger(handle_order_placed, get_contract_event('OrderLib', 'DexorderSwapPlaced'))
runner.add_event_trigger(handle_transfer, get_contract_event('ERC20', 'Transfer'))
@@ -71,9 +73,15 @@ def setup_logevent_triggers(runner):
runner.add_event_trigger(handle_dexorderexecutions, executions)
runner.add_event_trigger(activate_time_triggers)
runner.add_event_trigger(activate_price_triggers)
runner.add_event_trigger(process_active_tranches)
runner.add_event_trigger(process_execution_requests)
runner.add_event_trigger(handle_create_transactions)
runner.add_event_trigger(handle_send_transactions)
runner.add_event_trigger(create_transactions)
runner.add_event_trigger(send_transactions)
def init():
new_pool_prices.clear()
new_price_triggers.clear()
async def handle_order_placed(event: EventData):
@@ -169,17 +177,16 @@ def handle_transfer(transfer: EventData):
log.debug(f'vaults: {list(vaults)}')
new_pool_prices: dict[str, int] = {}
def handle_uniswap_swap(swap: EventData):
async def handle_uniswap_swap(swap: EventData):
try:
sqrt_price = swap['args']['sqrtPriceX96']
except KeyError:
return
addr = swap['address']
price = uniswap_price(sqrt_price)
price: dec = await uniswap_price(addr, sqrt_price)
log.debug(f'pool {addr} {price}')
new_pool_prices[addr] = price
pool_prices[addr] = price
def handle_vault_created(created: EventData):
@@ -204,22 +211,38 @@ def handle_vault_created(created: EventData):
current_pub.get()(f'{current_chain.get().chain_id}|{owner}', 'vaults', vaults)
def activate_time_triggers():
async def activate_time_triggers():
now = current_block.get().timestamp
log.debug(f'activating time triggers')
# time triggers
for tt in time_triggers:
tt(now)
await maywait(tt(now))
def activate_price_triggers():
log.debug('activating price triggers')
async def activate_price_triggers():
log.debug(f'activating price triggers')
pools_triggered = set()
for pool, price in new_pool_prices.items():
pools_triggered.add(pool)
for pt in price_triggers[pool]:
pt(price)
new_pool_prices.clear()
await maywait(pt(price))
for pool, triggers in new_price_triggers.items():
if pool not in pools_triggered:
price = pool_prices[pool]
for pt in triggers:
await maywait(pt(price))
for t in unconstrained_price_triggers:
# noinspection PyTypeChecker
t(None)
await maywait(t(None))
def process_active_tranches():
for tk, proof in active_tranches.items():
old_req = execution_requests.get(tk)
height = current_block.get().height
if old_req is None or old_req.height <= height:
log.info(f'execution request for {tk}')
execution_requests[tk] = ExecutionRequest(height, proof)
async def process_execution_requests():
height = current_block.get().height

View File

@@ -5,8 +5,7 @@ from enum import Enum
from typing import Optional
from dexorder import dec
from dexorder.blockchain.uniswap import uniswap_price
from dexorder.contract.uniswap_contracts import uniswapV3_pool_address
from dexorder.uniswap import uniswapV3_pool_address, uniswap_price
from dexorder.contract import abi_decoder, abi_encoder
log = logging.getLogger(__name__)
@@ -106,10 +105,8 @@ NO_OCO = 18446744073709551615 # max uint64
class ConstraintMode (Enum):
Time = 0
Limit = 1
Trailing = 2
Barrier = 3
Line = 4
Line = 1
Barrier = 2
@dataclass
class Constraint (ABC):
@@ -120,51 +117,16 @@ class Constraint (ABC):
mode = ConstraintMode(obj[0])
if mode == ConstraintMode.Time:
return TimeConstraint.load(obj[1])
elif mode == ConstraintMode.Line:
return LineConstraint.load(obj[1])
else:
raise NotImplementedError
raise ValueError(f'Unknown constraint mode {mode}')
@abstractmethod
def dump(self): ...
def _dump(self, types, values):
return self.mode, abi_encoder.encode(types, values)
class PriceConstraint (Constraint, ABC):
@abstractmethod
def passes(self, price: dec) -> bool:...
@dataclass
class LimitConstraint (PriceConstraint):
isAbove: bool
isRatio: bool
valueSqrtX96: int
TYPES = 'bool','bool','uint160'
def __init__(self, *args):
self.isAbove, self.isRatio, self.valueSqrtX96 = args
self.limit = uniswap_price(self.valueSqrtX96)
def load(self, obj):
isAbove, isRatio, valueSqrtX96 = abi_decoder.decode(LimitConstraint.TYPES, obj)
return LimitConstraint(ConstraintMode.Limit, isAbove, isRatio, valueSqrtX96)
def dump(self):
return self._dump(LimitConstraint.TYPES, (self.isAbove, self.isRatio, self.valueSqrtX96))
def passes(self, price: dec) -> bool:
return self.isAbove and price >= self.limit or not self.isAbove and price <= self.limit
@dataclass
class LineConstraint (Constraint):
isAbove: bool
isRatio: bool
time: int
valueSqrtX96: int
slopeSqrtX96: int
return self.mode.value, abi_encoder.encode(types, values)
class TimeMode (Enum):
@@ -201,6 +163,31 @@ class TimeConstraint (Constraint):
return self._dump(TimeConstraint.TYPES, (self.earliest.mode.value, self.earliest.time, self.latest.mode.value, self.latest.time))
@dataclass
class LineConstraint (Constraint):
isAbove: bool
isRatio: bool
time: int
valueSqrtX96: int
slopeSqrtX96: int
TYPES = 'bool','bool','uint32','uint160','int160'
@staticmethod
def load(obj):
return LineConstraint(ConstraintMode.Line, *abi_decoder.decode(LineConstraint.TYPES, obj))
def dump(self):
return self._dump(LineConstraint.TYPES, (self.isAbove, self.isRatio, self.time, self.valueSqrtX96, self.slopeSqrtX96))
async def passes(self, pool_addr: str, price: dec) -> bool:
limit = await uniswap_price(pool_addr, self.valueSqrtX96)
# todo slopes
# todo ratios
# prices AT the limit get zero volume, so we only trigger on >, not >=
return self.isAbove and price > limit or not self.isAbove and price < limit
@dataclass
class Tranche:
fraction: int # 18-decimal fraction of the order amount which is available to this tranche. must be <= 1

View File

@@ -1,24 +1,30 @@
import asyncio
import logging
from collections import defaultdict
from enum import Enum, auto
from typing import Callable
from typing import Callable, Optional, Union, Coroutine, Awaitable
from dexorder.blockstate import BlockSet, BlockDict
from .orderlib import TimeConstraint, LimitConstraint, ConstraintMode, SwapOrderState, PriceProof
from .orderlib import TimeConstraint, LineConstraint, ConstraintMode, SwapOrderState, PriceProof
from dexorder.util import defaultdictk
from .orderstate import Order
from .. import dec
from ..base.order import OrderKey, TrancheKey, ExecutionRequest
from ..data import pool_prices
from ..database.model.block import current_block
log = logging.getLogger(__name__)
# todo time and price triggers should be BlockSortedSets that support range queries, for efficient lookup of triggers
# todo time and price triggers should be BlockSortedSets that support range queries for efficient lookup of triggers
TimeTrigger = Callable[[int], None] # func(timestamp)
time_triggers:BlockSet[TimeTrigger] = BlockSet('tt')
PriceTrigger = Callable[[int], None] # func(pool_price)
PriceTrigger = Callable[[dec], Union[Awaitable[None],None]] # [async] func(pool_price)
price_triggers:dict[str, BlockSet[PriceTrigger]] = defaultdictk(lambda addr:BlockSet(f'pt|{addr}')) # different BlockSet per pool address
new_price_triggers:dict[str, set[PriceTrigger]] = defaultdict(set) # when price triggers are first set, they must be tested against the current price even if it didnt change this block
unconstrained_price_triggers: BlockSet[PriceTrigger] = BlockSet('upt') # tranches with no price constraints, whose time constraint is fulfilled
execution_requests:BlockDict[TrancheKey, ExecutionRequest] = BlockDict('e')
active_tranches: BlockDict[TrancheKey, Optional[PriceProof]] = BlockDict('at') # tranches which have passed all constraints and should be executed
execution_requests:BlockDict[TrancheKey, ExecutionRequest] = BlockDict('e') # generated by the active tranches
# todo should this really be blockdata?
inflight_execution_requests:BlockDict[TrancheKey, int] = BlockDict('ei') # value is block height when the request was sent
@@ -53,16 +59,16 @@ class TrancheTrigger:
return
time_constraint = None # stored as a tuple of two ints for earliest and latest absolute timestamps
self.price_constraints = []
self.line_constraints: list[LineConstraint] = []
for c in tranche.constraints:
if c.mode == ConstraintMode.Time:
c: TimeConstraint
earliest = c.earliest.timestamp(start)
latest = c.latest.timestamp(start)
time_constraint = (earliest, latest) if time_constraint is None else intersect_ranges(*time_constraint, earliest, latest)
elif c.mode == ConstraintMode.Limit:
c: LimitConstraint
raise NotImplementedError
elif c.mode == ConstraintMode.Line:
c: LineConstraint
self.line_constraints.append(c)
else:
raise NotImplementedError
self.time_constraint = time_constraint
@@ -92,8 +98,8 @@ class TrancheTrigger:
return
if now >= self.time_constraint[1]:
log.debug(f'tranche expired {self.tk}')
self.disable()
self.status = TrancheStatus.Expired
self.disable()
# check for all tranches expired
OrderTriggers.instances[self.order.key].check_complete()
elif self.status == TrancheStatus.Early and now >= self.time_constraint[0]:
@@ -102,30 +108,25 @@ class TrancheTrigger:
self.enable_price_trigger()
def enable_price_trigger(self):
if self.price_constraints:
if self.line_constraints:
price_triggers[self.order.pool_address].add(self.price_trigger)
new_price_triggers[self.order.pool_address].add(self.price_trigger)
else:
unconstrained_price_triggers.add(self.price_trigger)
def disable_price_trigger(self):
if self.price_constraints:
if self.line_constraints:
price_triggers[self.order.pool_address].remove(self.price_trigger)
else:
unconstrained_price_triggers.remove(self.price_trigger)
def price_trigger(self, cur):
async def price_trigger(self, cur: dec):
# must be idempotent. could be called twice when first activated: once for the initial price lookup then once again if that price was changed in the current block
if self.closed:
log.debug(f'price trigger ignored because trigger status is {self.status}')
return
if not self.price_constraints or all(pc.passes(cur) for pc in self.price_constraints):
self.execute()
def execute(self, proof: PriceProof = None):
old_req = execution_requests.get(self.tk)
height = current_block.get().height
if old_req is None or old_req.height <= height:
log.info(f'execution request for {self.tk}')
execution_requests[self.tk] = ExecutionRequest(height, proof)
if not self.line_constraints or all(await asyncio.gather(*[pc.passes(self.order.pool_address, cur) for pc in self.line_constraints])):
active_tranches[self.tk] = None # or PriceProof(...)
def fill(self, _amount_in, _amount_out ):
remaining = self.order.tranche_remaining(self.tk)
@@ -137,6 +138,10 @@ class TrancheTrigger:
return filled
def disable(self):
try:
del active_tranches[self.tk]
except KeyError:
pass
self.disable_time_trigger()
self.disable_price_trigger()

View File

@@ -50,7 +50,7 @@ def submit_transaction_request(tr: TransactionRequest):
return job
async def handle_create_transactions():
async def create_transactions():
for job in db.session.query(TransactionJob).filter(
TransactionJob.chain == current_chain.get(),
TransactionJob.state == TransactionJobState.Requested
@@ -75,7 +75,7 @@ async def create_transaction(job: TransactionJob):
log.info(f'servicing transaction request {job.request.__class__.__name__} {job.id} with tx {ctx.id}')
async def handle_send_transactions():
async def send_transactions():
w3 = current_w3.get()
for job in db.session.query(TransactionJob).filter(
TransactionJob.chain == current_chain.get(),

87
src/dexorder/uniswap.py Normal file
View File

@@ -0,0 +1,87 @@
from charset_normalizer.md import getLogger
from eth_utils import keccak, to_bytes, to_checksum_address
from dexorder import dec, db
from dexorder.base.chain import Ethereum, Polygon, Goerli, Mumbai, Arbitrum, Mock
from dexorder.blockchain import ByBlockchainDict
from dexorder.contract import abi_encoder, ContractProxy
from dexorder.contract.decimals import token_decimals
from dexorder.util import hexbytes
UNISWAPV3_POOL_INIT_CODE_HASH = hexbytes('0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54')
log = getLogger(__name__)
class Fee:
LOWEST = 100
LOW = 500
MEDIUM = 3000
HIGH = 10000
class UniswapV3Pool (ContractProxy):
def __init__(self, address: str = None):
super().__init__(address, 'IUniswapV3Pool')
async def price(self):
if not self.address:
raise ValueError
return await uniswap_price(self.address, (await self.slot0())[0])
def ordered_addresses(addr_a:str, addr_b:str):
return (addr_a, addr_b) if addr_a.lower() <= addr_b.lower() else (addr_b, addr_a)
def uniswap_pool_address(factory_addr: str, addr_a: str, addr_b: str, fee: int) -> str:
token0, token1 = ordered_addresses(addr_a, addr_b)
salt = keccak(abi_encoder.encode(['address','address','uint24'],[token0, token1, fee]))
contract_address = keccak(
b"\xff"
+ to_bytes(hexstr=factory_addr)
+ salt
+ UNISWAPV3_POOL_INIT_CODE_HASH
).hex()[-40:]
result = to_checksum_address(contract_address)
# log.debug(f'uniswap pool address {factory_addr} {addr_a} {addr_b} {fee} => {result}')
return result
async def uniswap_price(addr, sqrt_price) -> dec:
price = dec(sqrt_price*sqrt_price) / 2 ** (96 * 2)
decimals = await pool_decimals(addr)
return price * dec(10) ** dec(decimals)
async def pool_decimals(addr):
key = f'pd|{addr}'
try:
return db.kv[key]
except KeyError:
pool = UniswapV3Pool(addr)
token0 = await pool.token0()
token1 = await pool.token1()
decimals0 = await token_decimals(token0)
decimals1 = await token_decimals(token1)
decimals = decimals0 - decimals1
db.kv[key] = decimals
return decimals
class _UniswapContracts (ByBlockchainDict[ContractProxy]):
def __init__(self):
std = {
'factory': ContractProxy('0x1F98431c8aD98523631AE4a59f267346ea31F984', 'IUniswapV3Factory'),
'nfpm': ContractProxy('0xC36442b4a4522E871399CD717aBDD847Ab11FE88', 'INonfungiblePositionManager'),
'quoter': ContractProxy('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', 'IQuoter'),
'swap_router': ContractProxy('0xE592427A0AEce92De3Edee1F18E0157C05861564', 'ISwapRouter'),
}
super().__init__({chain.chain_id:std for chain in (Ethereum, Polygon, Goerli, Mumbai, Arbitrum, Mock)})
uniswapV3 = _UniswapContracts()
def uniswapV3_pool_address( addr_a: str, addr_b: str, fee: int):
return uniswap_pool_address(uniswapV3['factory'].address, addr_a, addr_b, fee)