GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Multihop feature request · Issue #69 · uniswap-python/uniswap-python

Multihop feature request #69

New issue
Copy link
New issue
Copy link
Closed
Closed
Multihop feature request#69
Copy link
Labels
enhancementNew feature or requestNew feature or request

Description

@MoneroCrusher
MoneroCrusher
opened on Mar 6, 2021
Issue body actions

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
    ErikBjare
    on Mar 7, 2021
    Member
    More actions

    You're right that it's not supported (and that it should be). Would require modifications to:

    https://github.com/shanefontaine/uniswap-python/blob/e58f3fb91dfca181630fb2732910a65a406b40db/uniswap/uniswap.py#L552

    https://github.com/shanefontaine/uniswap-python/blob/e58f3fb91dfca181630fb2732910a65a406b40db/uniswap/uniswap.py#L669

    https://github.com/shanefontaine/uniswap-python/blob/e58f3fb91dfca181630fb2732910a65a406b40db/uniswap/uniswap.py#L316

    https://github.com/shanefontaine/uniswap-python/blob/e58f3fb91dfca181630fb2732910a65a406b40db/uniswap/uniswap.py#L357

    Specifically, the lines looking like this:

    https://github.com/shanefontaine/uniswap-python/blob/e58f3fb91dfca181630fb2732910a65a406b40db/uniswap/uniswap.py#L703

    Should be pretty easy to fix by adding an optional route kwarg (with the desired route) or similar for all the mentioned functions.

    Not sure how you'd get the most efficient routing though (without brute force search). @shanefontaine do you know?

  2. shanefontaine commented on Mar 8, 2021

    @shanefontaine
    shanefontaine
    on Mar 8, 2021
    Collaborator
    More actions

    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
    MoneroCrusher
    on Mar 9, 2021
    Author
    More actions

    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
    liquid-8
    on Mar 11, 2021
    Member
    More actions

    I suppose that multihop feature is implemented here

    public static bestTradeExactIn
    adn next one method bestTradeExactOut

    Reacted by MoneroCrusher and BI-NOP
  5. MoneroCrusher commented on Mar 19, 2021

    @MoneroCrusher
    MoneroCrusher
    on Mar 19, 2021
    Author
    More actions

    @ErikBjare @shanefontaine Are you guys there?

  6. ErikBjare commented on Mar 20, 2021

    @ErikBjare
    ErikBjare
    on Mar 20, 2021
    Member
    More actions

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

  7. ErikBjare
    added
    enhancementNew feature or requestNew feature or request
    on Mar 20, 2021
  8. liquid-8 commented on Mar 22, 2021

    @liquid-8
    liquid-8
    on Mar 22, 2021
    Member
    More actions

    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.

    Reacted by Ihor and Amirhosein Soofi
  9. dustiwe commented on Mar 23, 2021

    @dustiwe
    dustiwe
    on Mar 23, 2021
    More actions

    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

  10. ErikBjare commented on May 4, 2021

    @ErikBjare
    ErikBjare
    on May 4, 2021
    Member
    More actions

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

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

  11. ErikBjare
    closed this as completedon May 4, 2021
  12. qraxiss commented on Mar 11, 2023

    @qraxiss
    qraxiss
    on Mar 11, 2023
    More actions

    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 requestNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.