Skip to content

Multihop feature request #69

Closed
Closed

Description

@MoneroCrusher

Hey I've noticed that the prices are always displayed for direct routing, in many cases this returns very bad prices with high slippages.
Is there already a method to get the most efficient routing or could you implement it? Thanks!

Activity

  1. ErikBjare commented on Mar 7, 2021

    @ErikBjare
    Member
  2. shanefontaine commented on Mar 8, 2021

    @shanefontaine
    Collaborator

    I believe the Uniswap router logic (off-chain) that chooses the path is closed source, unfortunately.

    With that said, I cannot find any data that confirms or denies this. I will try to figure out if it is open source today.

  3. MoneroCrusher commented on Mar 9, 2021

    @MoneroCrusher
    Author

    Thanks for the quick feedback guys!
    I'd think it would be open source since all the clones & forks also have the routing implemented (Quickswap and Sushiswap for example).

  4. liquid-8 commented on Mar 11, 2021

    @liquid-8
    Member

    I suppose that multihop feature is implemented here

    public static bestTradeExactIn
    adn next one method bestTradeExactOut

  5. MoneroCrusher commented on Mar 19, 2021

    @MoneroCrusher
    Author

    @ErikBjare @shanefontaine Are you guys there?

  6. ErikBjare commented on Mar 20, 2021

    @ErikBjare
    Member

    Yup, waiting for someone who wants this to make a PR!

  7. liquid-8 commented on Mar 22, 2021

    @liquid-8
    Member

    To my regret I can't make pr myself because my implementation differs too much but here is sort of the summary.

    Let's say we need the optimal route for swap (token0,token1). Please consider that any of them might be eth/weth.

    First, uniswap engine calls useAllCommonPairs(...) to get the list of possible routes. It doesn't contain EVERY possible route, just common ones. Key tokens are listed in BASES_TO_CHECK_TRADES_AGAINST. So, after that call we have list of the routes looking like [token0, basetoken0,token1], [token0, basetoken1,token1] etc. Please note that increasing hops count lead to dramatic growth of that list.

    Second, we pass the list to bestTradeExactIn/Out(...) accordingly that analizes routes one by one via recursive calls and returns list of N routes sorted from the best to to worst.

    Sure, this is not too easy to implement but also not THAT hard. Also remember that BASES_TO_CHECK_TRADES_AGAINST list varies from one network to another if you need alternate networks support like BSC/Heco/whatever.

    Hope that helps, regards.

  8. dustiwe commented on Mar 23, 2021

    @dustiwe

    To my regret I can't make pr myself because my implementation differs too much but here is sort of the summary.

    Let's say we need the optimal route for swap (token0,token1). Please consider that any of them might be eth/weth.

    First, uniswap engine calls useAllCommonPairs(...) to get the list of possible routes. It doesn't contain EVERY possible route, just common ones. Key tokens are listed in BASES_TO_CHECK_TRADES_AGAINST. So, after that call we have list of the routes looking like [token0, basetoken0,token1], [token0, basetoken1,token1] etc. Please note that increasing hops count lead to dramatic growth of that list.

    Second, we pass the list to bestTradeExactIn/Out(...) accordingly that analizes routes one by one via recursive calls and returns list of N routes sorted from the best to to worst.

    Sure, this is not too easy to implement but also not THAT hard. Also remember that BASES_TO_CHECK_TRADES_AGAINST list varies from one network to another if you need alternate networks support like BSC/Heco/whatever.

    Hope that helps, regards.

    thanks, I will think

  9. ErikBjare commented on May 4, 2021

    @ErikBjare
    Member

    I added initial (largely untested) support for this in #58.

    Closing this issue, opened #93 for finding the optimal trade route.

  10. qraxiss commented on Mar 11, 2023

    @qraxiss

    My solution is here. +- 1 in 1000 margin of error.

    If a PR is requested, I can write a more professional one. But for me this is enough.

    from concurrent.futures import ThreadPoolExecutor
    from collections import defaultdict
    from itertools import permutations
    from uniswap import Uniswap
    from web3 import Web3
    
    
    class Token:
        def __init__(self, address, name, symbol, decimals) -> None:
            self.address = Web3.toChecksumAddress(address)
            self._raw_address = address
            self.name = name
            self.symbol = symbol
            self.decimals = decimals
            self.qty = 10 ** self.decimals
    
    
    class UniswapPrice:
        sides = {
            'sell': 'get_price_output',
            'buy': 'get_price_input'
        }
    
        percentages = [50, 100]
        fees = [100, 300, 3000, 10000]
    
        def __init__(self, v2, v3):
            self.swap = {
                'v2': {
                    'exchange': v2,
                    'percentages': self.percentages,
                    'fees': [3000]
                },
                'v3': {
                    'exchange': v3,
                    'percentages': self.percentages,
                    'fees': self.fees
                }
            }
    
        def find_best(self):
            prices = [perm[0] + perm[1] for perm in permutations(
                self.pool_prices('different'), 2)] + self.pool_prices('single')
            if len(prices) == 0:
                return
            if self.side == 'sell':
                return min(prices)
            elif self.side == 'buy':
                return max(prices)
    
        def get_price(self, exchange, fee, qty):
            queue = [self.t0.address, self.t1.address] if self.side == 'buy' else [
                self.t1.address, self.t0.address]
            price_func = getattr(exchange, self.sides[self.side])
            return price_func(*queue, qty, fee=fee) / self.t1.qty
    
        def pool_prices(self, price_type):
            if price_type == 'single':
                perc = 100
            elif price_type == 'different':
                perc = 50
    
            return [
                fee[perc]
                for exchanges in self.futures.values()
                for fee in exchanges.values()
                if isinstance(fee[perc], float)
            ]
    
        def get_best_price(self, t0, t1, side):
            self.t0 = t0
            self.t1 = t1
            self.side = side
    
            self.get_prices()
            return self.find_best()
    
        def get_prices(self):
            self.futures = defaultdict(dict)
    
            with ThreadPoolExecutor(max_workers=10) as executor:
                for exchange_name, exchange_data in self.swap.items():
                    self.futures[exchange_name] = defaultdict(dict)
                    for fee in exchange_data['fees']:
                        self.futures[exchange_name][fee] = defaultdict(dict)
                        for percentage in exchange_data['percentages']:
                            self.futures[exchange_name][fee][percentage] = defaultdict(
                                dict)
                            qty = self.t0.qty * percentage // 100
                            self.futures[exchange_name][fee][percentage] = executor.submit(
                                self.get_price, exchange_data['exchange'], fee, qty
                            )
    
                for exchange_name, exchange_fees in self.futures.items():
                    self.futures[exchange_name] = dict(exchange_fees)
                    for fee, percentage_data in exchange_fees.items():
                        self.futures[exchange_name][fee] = dict(percentage_data)
                        for percentage, future in percentage_data.items():
                            try:
                                self.futures[exchange_name][fee][percentage] = future.result(
                                )
                            except:
                                pass
    
    
    web3 = Web3(Web3.HTTPProvider("YOUR-JSON-RPC-URL"))
    
    uni_v2 = Uniswap(
        address=None,
        private_key=None,
        version=2,
        web3=web3
    )
    
    uni_v3 = Uniswap(
        address=None,
        private_key=None,
        version=3,
        web3=web3
    )
    
    t0 = Token(**{
                   "symbol": "ETH",
                   "name": "Wrapped Ethereum",
                   "decimals": 18,
                   "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
               }
    )
    
    
    t1 = Token(**{
                   "symbol": "WBTC",
                   "name": "Wrapped Bitcoin",
                   "decimals": 8,
                   "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"
               }
    )
    
    
    price_calculator = UniswapPrice(uni_v2, uni_v3)
    
    print(price_calculator.get_best_price(t0, t1, 'buy'),
          price_calculator.get_best_price(t0, t1, 'sell'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions