예제 #1
0
def test_token_functions(token='ETH'):
    print("\n", token_utils.ten_to_the_decimals(token))
    print("\n", token_utils.tokens_by_addr())
    print("\n", token_utils.addr(token))
    token_utils.addr(token.lower())  # should not raise keyError
    print("\n", token_utils.int_amount(1.2, token))
    print("\n", token_utils.real_amount(123456789, token))
예제 #2
0
def get_curve_info(j):
    '''Returns an array of swaps and the info related to their best routes for the given JSON'''
    curve_info = []
    # sd = totle_client.swap_data(j['response']['response'], True)

    respresp = j['response']['response']
    response_id = respresp['id']
    # if len(respresp['summary']) > 1: raise ValueError(f"response response has multiple summaries")
    summary = respresp['summary'][0]
    summary_source_asset = summary['sourceAsset']['symbol']
    summary_source_amount = token_utils.real_amount(summary['sourceAmount'], summary_source_asset)
    summary_rate = float(summary['rate'])

    for swap in j['swaps']:
        swap_data = {}

        swap_source_asset = swap['sourceAsset']['symbol']
        swap_source_amount = token_utils.real_amount(swap['sourceAmount'], swap_source_asset)

        if swap_source_asset == summary_source_asset and round(swap_source_amount) != round(summary_source_amount):
            raise ValueError(f"swap_source_amount={round(swap_source_amount)} {swap_source_asset} DOES NOT EQUAL summary_source_amount {round(summary_source_amount)} {summary_source_asset}")

        viable_routes = swap['routes']

        for i, route in enumerate(viable_routes):
            route_source_asset = route['sourceAsset']['symbol']
            if route_source_asset != swap_source_asset:
                raise ValueError(f"route source asset={route_source_asset} BUUUUUT swap_source_asset={swap_source_asset}")
            if route['rate'] == swap['rate'] and swap_source_amount - token_utils.real_amount(route['sourceAmount'], route['sourceAsset']['symbol']) < 0.5:
                used_route = route
            #
            # route_data = {
            #     'source_asset': route['sourceAsset']['symbol'],
            #     'destination_asset': route['destinationAsset']['symbol'],
            #     'real_source_amount': token_utils.real_amount(route['sourceAmount'], route_source_asset),
            #     'real_destination_amount': token_utils.real_amount(route['destinationAmount'], route['destinationAsset']['symbol']),
            #     'num_trades': len(route['trades']),
            #     'rate': float(route['rate']),
            #     'trades': get_trades_info(route['trades'])
            # }
            # print(f"DEBUG Route {i} = { route_data['real_source_amount']} {route_data['source_asset']} gets {round(route_data['real_destination_amount'])} {route_data['destination_asset']} price={1 / route_data['rate']:.6g} rate={route_data['rate']:.6g} using {route_data['num_trades']} trades")

        # look for an aborted route that would have been better
        better_route = max(viable_routes, key=lambda r: float(r['rate']))
        route_source_asset = better_route['sourceAsset']['symbol']
        route_source_amount = token_utils.real_amount(better_route['sourceAmount'], route_source_asset)
        # This (mostly) handles the case of a broken route with less than trade size source amount
        if route_source_asset == summary_source_asset and route_source_amount < summary_source_amount:
            trades_info = get_trades_info(better_route['trades'], for_realz_source_amount=summary_source_amount)
        else:
            trades_info = get_trades_info(better_route['trades'])

        swap_data['better_route'] = {
            'source_asset': route_source_asset,
            'destination_asset': better_route['destinationAsset']['symbol'],
            'real_source_amount': route_source_amount,
            'real_destination_amount': token_utils.real_amount(better_route['destinationAmount'], better_route['destinationAsset']['symbol']),
            'num_trades': len(better_route['trades']),
            'rate': float(better_route['rate']),
            'trades': trades_info
        }

        # filter out routes that don't have enough liquidity (sourceAmount)
        viable_routes = filter(lambda route: swap_source_amount - token_utils.real_amount(route['sourceAmount'], route['sourceAsset']['symbol']) < 0.5, viable_routes)
        best_route = max(viable_routes, key=lambda r: float(r['rate']))

        # if best_route_source_asset == swap_source_asset and round(best_route_source_amount) != round(swap_source_amount):
        #     raise ValueError(f"best_route_source_asset={round(best_route_source_amount)} {best_route_source_asset} DOES NOT EQUAL swap_source_amount {round(swap_source_amount)} {swap_source_asset}")

        swap_data['used_route'] = {
            'source_asset': used_route['sourceAsset']['symbol'],
            'destination_asset': used_route['destinationAsset']['symbol'],
            'real_source_amount': token_utils.real_amount(used_route['sourceAmount'], used_route['sourceAsset']['symbol']),
            'real_destination_amount': token_utils.real_amount(used_route['destinationAmount'], used_route['destinationAsset']['symbol']),
            # 'real_source_amount': swap_source_amount,
            # 'real_destination_amount': token_utils.real_amount(swap['destinationAmount'], swap['destinationAsset']['symbol']),
            'num_trades': len(used_route['trades']),
            'rate': float(used_route['rate']),
            'trades': get_trades_info(used_route['trades'])
        }

        swap_data['best_route'] = {
            'source_asset': best_route['sourceAsset']['symbol'],
            'destination_asset': best_route['destinationAsset']['symbol'],
            'real_source_amount': token_utils.real_amount(best_route['sourceAmount'], best_route['sourceAsset']['symbol']),
            'real_destination_amount': token_utils.real_amount(best_route['destinationAmount'], best_route['destinationAsset']['symbol']),
            'num_trades': len(best_route['trades']),
            'rate': float(best_route['rate']),
            'trades': get_trades_info(best_route['trades'])
        }
        
        if swap_data['best_route']['rate'] != summary_rate:
            print(f"\n*********************\nDIFF RATE: best_route rate={swap_data['best_route']['rate']} summary_rate={summary_rate}\n{response_id}\n")

        # sdbr = swap_data['best_route']
        # print(f"DEBUG BEST Route = { sdbr['real_source_amount']} {sdbr['source_asset']} gets {round(sdbr['real_destination_amount'])} {sdbr['destination_asset']} price={1 / sdbr['rate']:.6g} rate={sdbr['rate']:.6g} using {sdbr['num_trades']} trades")

        curve_info.append(swap_data)

    return curve_info
예제 #3
0
def get_trades_info(trades, for_realz_source_amount=None):
    trade_datas = []
    for trade in trades:
        trade_data = {
            'source_asset': trade['sourceAsset']['symbol'],
            'destination_asset': trade['destinationAsset']['symbol'],
            'real_source_amount': token_utils.real_amount(trade['sourceAmount'], trade['sourceAsset']['symbol']),
            'real_destination_amount': token_utils.real_amount(trade['destinationAmount'], trade['destinationAsset']['symbol']),
            'rate': float(trade['rate']),
            'orders': [],
            'splits': []
        }
        # print(f"DEBUG Trade n from {trade_data['source_asset']} to {trade_data['destination_asset']}")
        # print("---------------------")
        # print(json.dumps(trade, indent=3))
        # print("---------------------")

        rates, order_source_amounts = {}, {}  # need to extract these from orders and plug them in to split['rate']
        for order in trade['orders']['main']:
            dex = order['exchangeId']
            order_source_amount = token_utils.real_amount(order['sourceAmount'], order['sourceAsset']['symbol'])
            rates[exchange_name(dex)] = float(order['rate'])
            order_source_amounts[exchange_name(dex)] = order_source_amount

            order_data = {
                'source_asset': order['sourceAsset']['symbol'],
                'destination_asset': order['destinationAsset']['symbol'],
                'real_source_amount': order_source_amount,
                'real_destination_amount': token_utils.real_amount(order['destinationAmount'], order['destinationAsset']['symbol']),
                'dex': exchange_name(dex),
                'pct': order['splitPercentage'],
                'rate': float(order['rate']),
            }
            # print(f"   DEBUG Order n on {order_data['dex']} from {order_data['source_asset']} to {order_data['destination_asset']}")
            trade_data['orders'].append(order_data)

        for s in trade['split']:
            dex = exchange_name(s['exchangeId'])
            pct = float(s['percentage'])
            if not pct: # sometimes there are 0% splits with DEXs that are not in rates because there are no orders for that DEX
                print(f"   WARNING: skipping {dex} with {pct}% split")
                continue

            # TODO: check that routesummary_source_amount
            # we use max() because sometimes the trade source amount is less than the actual trade size on which the trade
            # was based. If the order source amount was larger, it is often equal to the actual amount intended in the split
            real_split_source_amount = (for_realz_source_amount or trade_data['real_source_amount']) * pct / 100
            amt_rates = [[float(amt), float(rate)] for amt, rate in s['dataPoints']]

            # print(f"   DEBUG: {dex} ({pct}% split) spends {round(real_split_source_amount)} {trade_data['source_asset']} to get {trade_data['destination_asset']} at rate = {rates[dex]})")

            split_low_index = -1  # sometimes the dataPoints start at an amount > trade size
            for i, amt_rate in enumerate(amt_rates):
                if amt_rate[0] < real_split_source_amount: split_low_index = i
            split_high_index, split_last_index = split_low_index + 1, len(amt_rates) - 1

            # print(f"\nDEBUG {dex} dataPoints")
            # for amt_rate in amt_rates: print_amt_rate(amt_rate)

            split_data = {
                'dex': dex,
                'pct': pct,
                'rate': rates[dex],
                'real_source_amount': real_split_source_amount,
                'low_index': split_low_index,
                'high_index': split_high_index,
                'last_index': split_last_index,
                'curve': {'min': amt_rates[0],
                          'split_low': amt_rates[split_low_index] if split_low_index > 0 else None,
                          'split_high': amt_rates[split_high_index] if split_high_index < split_last_index else None,
                          'max': amt_rates[-1]
                          },
            }

            trade_data['splits'].append(split_data)

        trade_datas.append(trade_data)
    return trade_datas
예제 #4
0
def get_quote(from_token,
              to_token,
              from_amount=None,
              to_amount=None,
              dex=None,
              verbose=False,
              debug=False):
    """Returns the price in terms of the from_token - i.e. how many from_tokens to purchase 1 to_token"""
    if to_amount or not from_amount:
        raise ValueError(f"{name()} only works with from_amount")
    for t in [from_token, to_token]:
        if t != 'ETH' and t not in supported_tokens():
            return {}  # temporary speedup

    # https://api.1inch.exchange/v1.1/quote?fromTokenSymbol=ETH&toTokenSymbol=DAI&amount=100000000000000000000&disabledExchangesList=Bancor
    query = {
        'fromTokenSymbol': from_token,
        'toTokenSymbol': to_token,
        'amount': token_utils.int_amount(from_amount, from_token)
    }
    r = None
    try:
        r = requests.get(QUOTE_ENDPOINT, params=query)
        if debug:
            print(f"r.status_code={r.status_code}")
        j = r.json()
        if debug:
            print(
                f"REQUEST to {QUOTE_ENDPOINT}:\n{json.dumps(query, indent=3)}\n\n"
            )
            print(
                f"RESPONSE from {QUOTE_ENDPOINT}:\n{json.dumps(j, indent=3)}\n\n"
            )

        if j.get('message'):
            print(
                f"{name()}.{sys._getframe(  ).f_code.co_name} returned {j['message']} request was {query} response was {j}"
            )

            time.sleep(
                1.0 + random.random()
            )  # block each thread for 1-2 seconds to keep from getting rate limited
            return {}
        else:
            # Response:
            # {"fromToken":{"symbol":"ETH","name":"Ethereum","decimals":18,"address":"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"},
            #  "toToken":{"symbol":"DAI","name":"DAI","address":"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359","decimals":18},
            #  "toTokenAmount":"17199749926766572897346",
            #  "fromTokenAmount":"100000000000000000000",
            #  "exchanges":[{"name":"Oasis","part":66},{"name":"Radar Relay","part":0},{"name":"Uniswap","part":17},{"name":"Kyber","part":7},{"name":"Other 0x","part":10},{"name":"AirSwap","part":0}]}
            source_token = j['fromToken']['symbol']
            source_amount = token_utils.real_amount(j['fromTokenAmount'],
                                                    source_token)
            destination_token = j['toToken']['symbol']
            destination_amount = token_utils.real_amount(
                j['toTokenAmount'], destination_token)
            price = source_amount / destination_amount if destination_amount else 0.0
            exchanges_parts = {
                ex['name']: ex['part']
                for ex in j['exchanges'] if ex['part']
            }

            time.sleep(
                1.0 + random.random()
            )  # block each thread for 1-2 seconds to keep from getting rate limited
            return {
                'source_token': source_token,
                'source_amount': source_amount,
                'destination_token': destination_token,
                'destination_amount': destination_amount,
                'price': price,
                'exchanges_parts': exchanges_parts,
            }

    except (ValueError, requests.exceptions.RequestException) as e:
        if r is None:
            print(f"Failed to connect: #{e}")
        elif r.status_code == 429:
            print(f"RATE LIMITED {name()} {query}")
            time.sleep(300)
        else:
            print(
                f"{name()} {query} raised {e}: {r.text[:128] if r else 'no JSON returned'} status_code={r.status_code}"
            )
            if debug:
                print(
                    f"FAILED REQUEST to {QUOTE_ENDPOINT}:\n{json.dumps(query, indent=3)}\n\n"
                )
        return {}
예제 #5
0
def get_quote(from_token, to_token, from_amount=None, to_amount=None, dex=None, verbose=False, debug=False):
    """Returns the price in terms of the from_token - i.e. how many from_tokens to purchase 1 to_token"""
    if to_amount or not from_amount: raise ValueError(f"{name()} only works with from_amount")

    # these addresses are case-sensitive so we have to use paraswap_addr to map them.
    from_addr, to_addr = paraswap_addr(from_token), paraswap_addr(to_token)
    for addr, token in [(from_addr, from_token), (to_addr, to_token)]:
        if not addr: # token is not supported
            print(f"{token} could not be mapped to case-sensitive {name()} address")
            return {}

    # Request: from ETH to DAI
    # https://paraswap.io/api/v1/prices/1/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359/10000000000000000
    req_url = f"{PRICES_ENDPOINT}/{from_addr}/{to_addr}/{token_utils.int_amount(from_amount, from_token)}"
    if debug: print(f"REQUEST to {req_url}: (from_token={from_token}, to_token={to_token} from_amount={from_amount})\n\n")

    r = None
    try:
        r = requests.get(req_url)
        j = r.json()
        if debug: print(f"RESPONSE from {PRICES_ENDPOINT}:\n{json.dumps(j, indent=3)}\n\n")

        # Response:
        # {"priceRoute": {
        #     "amount": "1811400076272265830",
        #     "bestRoute": [
        #          {"exchange": "BANCOR", "percent": "100", "srcAmount": "10000000000000000", "amount": "1811400076272265830"},
        #          {"exchange": "UNISWAP", "percent": "0", "srcAmount": "0", "amount": "1807813865444263126"},
        #          {"exchange": "KYBER", "percent": "0", "srcAmount": "0", "amount": "1804732523842902460"},
        #          {"exchange": "ETH2DAI", "percent": "0", "srcAmount": "0", "amount": "1801799999999999999"},
        #          {"exchange": "COMPOUND", "percent": "0", "srcAmount": "0", "amount": "0"}],
        #     "others": [ ... ] }}

        price_route = j.get('priceRoute')
        if not price_route:
            print(f"{sys._getframe(  ).f_code.co_name} had no priceRoute request was {req_url} response was {j}")
            return {}
        else:
            exchanges_parts, exchanges_prices = {}, {}
            source_amount = from_amount
            destination_amount = token_utils.real_amount(price_route['amount'], to_token)
            if destination_amount == 0:
                print(f"{name()} destination_amount={0} price_route={json.dumps(price_route, indent=3)}")
                return {}

            price = source_amount / destination_amount
            for dex_alloc in price_route['bestRoute']:
                # We use custom int_conv because sometimes Paraswap splits produce amounts expressed as strings with
                # decimal points causing int(s) to raise "invalid literal for int() with base 10"
                dex, pct, src_amt, dest_amt = dex_alloc['exchange'], int(dex_alloc['percent']), int_conv(dex_alloc['srcAmount']), int_conv(dex_alloc['amount'])
                if pct > 0:
                    exchanges_parts[dex] = pct

                # Don't try to compute exchanges_prices because these amounts are wack. They add up to more than
                # destination_amount, and are sometimes 0 even when percent > 0
                #
                # if dest_amt > 0: # a price quote with amount=0 is not a price quote
                #     # when price quotes have srcAmount=0 use the source_amount
                #     real_src_amt = token_utils.real_amount(src_amt, from_token) if src_amt > 0 else source_amount
                #     exchanges_prices[dex] = real_src_amt / token_utils.real_amount(dest_amt, to_token)

            return {
                'source_token': from_token,
                'source_amount': source_amount,
                'destination_token': to_token,
                'destination_amount': destination_amount,
                'price': price,
                'exchanges_parts': exchanges_parts,
                # 'exchanges_prices': exchanges_prices
            }



    except (ValueError, requests.exceptions.RequestException) as e:
        print(f"{name()} {req_url} raised {e}: {r.text[:128] if r else 'no JSON returned'}")
        return {}
def get_quote(from_token,
              to_token,
              from_amount=None,
              to_amount=None,
              dex=None,
              verbose=False,
              debug=False):
    """Returns the price in terms of the from_token - i.e. how many from_tokens to purchase 1 to_token"""
    endpoint = QUOTE_ENDPOINT

    if to_amount or not from_amount:
        raise ValueError(f"{name()} only works with from_amount")
    for t in [from_token, to_token]:
        if t != 'ETH' and t not in supported_tokens():
            return {}  # temporary speedup

    from_token_addr, to_token_addr = oneinch_v2_addr(
        from_token), oneinch_v2_addr(to_token)

    query = {
        'fromTokenAddress': from_token_addr,
        'toTokenAddress': to_token_addr,
        'amount': token_utils.int_amount(from_amount, from_token)
    }
    r = None
    try:
        r = requests.get(endpoint, params=query)
        if debug:
            print(f"r.status_code={r.status_code}")
        j = r.json()
        if debug:
            print(f"REQUEST to {endpoint}:\n{json.dumps(query, indent=3)}\n\n")
            print(f"RESPONSE from {endpoint}:\n{json.dumps(j, indent=3)}\n\n")

        if j.get('message'):
            print(
                f"{name()}.{sys._getframe(  ).f_code.co_name} returned {j['message']}. Request was {query} response was {j}"
            )
            time.sleep(
                1.0 + random.random()
            )  # block each thread for 1-2 seconds to keep from getting rate limited
            return {}

        if j.get('errors'):
            # j = {'errors': [{'msg': 'error'}]}
            print(
                f"{name()}.{sys._getframe(  ).f_code.co_name} returned {j['errors'][0]['msg']}. Request was {query} response was {j}"
            )
            time.sleep(
                1.0 + random.random()
            )  # block each thread for 1-2 seconds to keep from getting rate limited
            return {}

        else:
            # Response:
            # {
            #   "fromToken": {
            #     "symbol": "USDT",
            #     "name": "Tether USD",
            #     "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
            #     "decimals": 6,
            #     "logoURI": "https://tokens.1inch.exchange/0xdac17f958d2ee523a2206206994597c13d831ec7.png"
            #   },
            #   "toToken": {
            #     "symbol": "WBTC",
            #     "name": "Wrapped BTC",
            #     "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
            #     "decimals": 8,
            #     "logoURI": "https://tokens.1inch.exchange/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599.png"
            #   },
            #   "toTokenAmount": "53797189",  // result amount of WBTC (0.538 WBTC)
            #   "fromTokenAmount": "10000000000",
            #   "protocols": [
            #     [
            #       [
            #         {
            #           "name": "CURVE",
            #           "part": 100,
            #           "fromTokenAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",
            #           "toTokenAddress": "0x6b175474e89094c44da98b954eedeac495271d0f"
            #         }
            #       ],
            #       [
            #         {
            #           "name": "SUSHI",
            #           "part": 100,
            #           "fromTokenAddress": "0x6b175474e89094c44da98b954eedeac495271d0f",
            #           "toTokenAddress": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
            #         }
            #       ],
            #       [
            #         {
            #           "name": "BALANCER",
            #           "part": 100,
            #           "fromTokenAddress": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
            #           "toTokenAddress": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"
            #         }
            #       ]
            #     ]
            #   ],
            #   "estimatedGas": 590385   // do not use gas limit from the quote method
            # }

            try:
                source_token = j['fromToken']['symbol']
                source_amount = token_utils.real_amount(
                    j['fromTokenAmount'], source_token)
                destination_token = j['toToken']['symbol']
                destination_amount = token_utils.real_amount(
                    j['toTokenAmount'], destination_token)
            except KeyError as e:
                print(f"\n\nj = {j}\n\n")
                raise
            price = source_amount / destination_amount if destination_amount else 0.0

            routes = j['protocols']
            if len(routes) == 1:
                exchanges_parts = parse_split_route(routes[0])
            else:  # multiple routes
                exchanges_parts = [
                    parse_split_route(route) for route in routes
                ]

            time.sleep(
                1.0 + random.random()
            )  # block each thread for 1-2 seconds to keep from getting rate limited
            return {
                'source_token': source_token,
                'source_amount': source_amount,
                'destination_token': destination_token,
                'destination_amount': destination_amount,
                'price': price,
                'exchanges_parts': exchanges_parts,
            }

    except (ValueError, requests.exceptions.RequestException) as e:
        if r is None:
            print(f"Failed to connect: #{e}")
        elif r.status_code == 429:
            print(f"RATE LIMITED {name()} {query}")
            time.sleep(300)
        else:
            print(
                f"{name()} {query} raised {e}: {r.text[:128] if r else 'no JSON returned'} status_code={r.status_code}"
            )
            if debug:
                print(
                    f"FAILED REQUEST to {endpoint}:\n{json.dumps(query, indent=3)}\n\n"
                )
        return {}
예제 #7
0
def get_swap(from_token,
             to_token,
             from_amount=None,
             to_amount=None,
             dex=None,
             from_address=None,
             slippage=50,
             verbose=False,
             debug=False):
    """Returns the price in terms of the from_token - i.e. how many from_tokens to purchase 1 to_token"""

    # Request: from ETH to DAI
    # https://api.0x.org/swap/v0/quote?buyToken=DAI&sellToken=ETH&buyAmount=1000000000000000000

    query = {
        'sellToken': from_token,
        'buyToken': to_token,
        'slippagePercentage': slippage / 100
    }  # slippagePercentage is really just a fraction (default = 0.01)
    if from_amount and to_amount:
        raise ValueError(
            f"{name()} only accepts either from_amount or to_amount, not both")
    elif from_amount:
        query['sellAmount'] = token_utils.int_amount(from_amount, from_token)
    elif to_amount:
        query['buyAmount'] = token_utils.int_amount(to_amount, to_token)
    else:
        raise ValueError(
            f"{name()}: either from_amount or to_amount must be specified")

    if from_address:
        query['takerAddress'] = from_address

    if debug:
        print(
            f"REQUEST to {SWAP_ENDPOINT}:\n{json.dumps(query, indent=3)}\n\n")
    r = None
    try:
        r = requests.get(SWAP_ENDPOINT, params=query)
        j = r.json()
        if debug:
            print(
                f"RESPONSE from {SWAP_ENDPOINT}:\n{json.dumps(j, indent=3)}\n\n"
            )

        # Response:
        # {
        #   "price": "0.00607339681846613",
        #   "to": "0x61935cbdd02287b511119ddb11aeb42f1593b7ef",
        #   "data": "0x8bc8efb3000000..."
        #   "value": "750000000000000",
        #   "gasPrice": "5000000000",
        #   "protocolFee": "750000000000000",
        #   "buyAmount": "100000000000000000",
        #   "sellAmount": "607339681846613",
        #   "orders": []

        if not j.get('price'):
            if verbose:
                print(
                    f"FAILURE RESPONSE from {SWAP_ENDPOINT}:\n{json.dumps(j, indent=3)}\n\n"
                )
            return {}

        zrx_price = float(j['price'])
        # We always want to return price in terms of how many from_tokens for 1 to_token, which means we need to
        # invert 0x's price whenever from_amount is specified.
        price = 1 / zrx_price if from_amount else zrx_price

        source_amount = token_utils.real_amount(j['sellAmount'], from_token)
        destination_amount = token_utils.real_amount(j['buyAmount'], to_token)

        fee_amount = token_utils.real_amount(j['protocolFee'], from_token)
        if verbose: print(f"0x fee = {100 * fee_amount/source_amount:.4f}%")

        exchanges_parts = {}
        for source in j['sources']:
            exchanges_parts[source['name']] = 100 * float(source['proportion'])

        payload = j['orders']

        return {
            'source_token': from_token,
            'source_amount': source_amount,
            'destination_token': to_token,
            'destination_amount': destination_amount,
            'price': price,
            'exchanges_parts': exchanges_parts,
            'payload': payload,
            # 'exchanges_prices': exchanges_prices
        }

    except (ValueError, requests.exceptions.RequestException) as e:
        print(
            f"{name()} {query} raised {e}: {r.text[:128] if r else 'no JSON returned'}"
        )
        return {}