vault.valuation

Documentation for eth_defi.vault.valuation Python module.

Net asset valuation calculations for token portfolios and vaults.

  • Calculate the value of vault portfolio using only onchain data, available from JSON-RPC

  • Find best routes to buy tokens, which result to the best price, using brute force

  • See NetAssetValueCalculator for usage

Classes

NetAssetValueCalculator

Calculate valuation of all vault spot assets, assuming we would sell them on Uniswap market sell or similar.

PortfolioValuation

Valuation calulated for a portfolio.

Route

One potential swap path.

SwapMatrix

Brute-forced route swap result for a portfolio of buying multiple tokens.

UniswapV2Router02Quoter

Handle Uniswap v2 quoters using Router02 contract.

UniswapV3Quoter

Handle Uniswap v3 quoters using QuoterV2 contract.

ValuationMulticallWrapper

Wrap the undertlying Multicall with diagnostics data.

ValuationQuoter

Handle asset valuation on a specific DEX/quoter.

Exceptions

NoRouteFound

We could not route some of the spot tokens to get any valuations for them.

exception NoRouteFound

Bases: Exception

We could not route some of the spot tokens to get any valuations for them.

__init__(*args, **kwargs)
__new__(**kwargs)
add_note()

Exception.add_note(note) – add a note to the exception

with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class PortfolioValuation

Bases: object

Valuation calulated for a portfolio.

See eth_defi.vault.base.VaultPortfolio for the portfolio itself.

denomination_token: eth_defi.token.TokenDetails

The reserve currency of this vault

spot_valuations: dict[eth_typing.evm.HexAddress, decimal.Decimal]

Individual spot valuations

get_total_equity()

How much we value this portfolio in the denomination_token

Return type

decimal.Decimal

__init__(denomination_token, spot_valuations)
Parameters
Return type

None

class SwapMatrix

Bases: object

Brute-forced route swap result for a portfolio of buying multiple tokens.

See NetAssetValueCalculator.find_swap_routes()

results: dict[eth_defi.vault.valuation.Route, decimal.Decimal | None]

Outcome of different attempted routes.

Result is none if the path did not exist or the smart contract call failed.

__init__(results, best_results_by_token)
Parameters
Return type

None

class Route

Bases: object

One potential swap path.

  • Support paths with 2 or 3 pairs

  • Present one potential swap path between source and target

  • Routes can contain any number of intermediate tokens in the path

  • Used to ABI encode for multicall calls

quoter: eth_defi.vault.valuation.ValuationQuoter

What router we use

path: tuple[eth_defi.token.TokenDetails, eth_defi.token.TokenDetails] | tuple[eth_defi.token.TokenDetails, eth_defi.token.TokenDetails, eth_defi.token.TokenDetails]

What route path we take

fees: tuple[int] | tuple[int, int] | None

Fees between pools for Uni v3

get_formatted_path()

Return human readable path.

Return type

str

__init__(quoter, path, fees=None)
Parameters
Return type

None

class ValuationMulticallWrapper

Bases: eth_defi.event_reader.multicall_batcher.MulticallWrapper

Wrap the undertlying Multicall with diagnostics data.

  • Because the underlying Multicall lib is not powerful enough.

  • And we do not have time to fix it

get_key()

Get key that will identify this call in the result dictionary

Return type

Hashable

create_multicall()

Create underlying call about.

Return type

multicall.call.Call

handle(success, raw_return_value)

Parse the call result.

Parameters
  • succeed – Did we revert or not

  • raw_return_value (bytes) – Undecoded bytes from the Solidity function call

Returns

The value placed in the return dict

Return type

decimal.Decimal | None

__init__(call, debug, quoter, route, amount_in)
Parameters
Return type

None

get_human_args()

Get Solidity args as human readable string for debugging.

Return type

str

multicall_callback(succeed, raw_return_value)

Convert the raw Solidity function call result to a denominated token amount.

  • Multicall library callback

Returns

The token amount in the reserve currency we get on the market sell.

None if this path was not supported (Solidity reverted).

Parameters
  • succeed (bool) –

  • raw_return_value (Any) –

Return type

Any

class ValuationQuoter

Bases: abc.ABC

Handle asset valuation on a specific DEX/quoter.

  • Takes in source and target tokens as input and generate all routing path combinations

  • Creates routes to a specific DEX

  • Each DEX has its own quoter contract we need to integrate

  • Resolves the onchain Solidity function return value to a token amount we get

__init__(debug=False)
Parameters

debug (bool) –

abstract format_path(route)

Get human-readable route path line.

Parameters

route (eth_defi.vault.valuation.Route) –

Return type

str

abstract classmethod dex_hint()

Return string id used to identify this DEX.

E.g. uniswap-v2.

Return type

str

class UniswapV2Router02Quoter

Bases: eth_defi.vault.valuation.ValuationQuoter

Handle Uniswap v2 quoters using Router02 contract.

signature_string = 'getAmountsOut(uint256,address[])(uint256[])'

Quoter signature string for Multicall lib.

Not the standard string signature format, because Multicall lib wants it special output format suffix here

__init__(swap_router_v2, debug=False)
Parameters
  • swap_router_v2 (web3.contract.contract.Contract) –

  • debug (bool) –

generate_routes(source_token, target_token, intermediate_tokens, amount, debug)

Create routes we need to test on Uniswap v2

Parameters
Return type

Iterable[eth_defi.vault.valuation.Route]

handle_onchain_return_value(wrapper, raw_return_value)

Convert getAmountsOut() return value to tokens we receive

Parameters
Return type

decimal.Decimal | None

get_path_combinations(source_token, target_token, intermediate_tokens)

Generate Uniswap v2 swap paths with all supported intermediate tokens

Parameters
Return type

Iterable[list[eth_defi.token.TokenDetails]]

format_path(route)

Get human-readable route path line.

Return type

str

class UniswapV3Quoter

Bases: eth_defi.vault.valuation.ValuationQuoter

Handle Uniswap v3 quoters using QuoterV2 contract.

signature_string = 'quoteExactInput(bytes,uint256)(uint256,uint160[],uint32[],uint256)'

Quoter signature string for Multicall lib.

https://basescan.org/address/0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a#code

__init__(quoter, debug=False, fee_hook=<function _fee_hook>)
Parameters
  • quoter (web3.contract.contract.Contract) –

  • debug (bool) –

generate_routes(source_token, target_token, intermediate_tokens, amount, debug)

Create routes we need to test on Uniswap v2

Parameters
Return type

Iterable[eth_defi.vault.valuation.Route]

handle_onchain_return_value(wrapper, raw_return_value)

Convert swapExactTokensForTokens() return value to tokens we receive

Parameters
Return type

decimal.Decimal | None

get_path_combinations(source_token, target_token, intermediate_tokens)

Generate Uniswap v3 swap paths and fee with all supported intermediate tokens

Parameters
Return type

Iterable[tuple[list[eth_typing.evm.HexAddress], list[int]]]

format_path(route)

Get human-readable route path line.

Return type

str

class NetAssetValueCalculator

Bases: object

Calculate valuation of all vault spot assets, assuming we would sell them on Uniswap market sell or similar.

  • Query valuations using only onchain data / direct quoter smart contracts, no external indexers or services needed

  • Price impact and fees included

  • Brute forces all possible route combinations

  • Pack more RPC punch by using Multicall library

Note

Early prototype code.

Example:

vault = lagoon_vault

universe = TradingUniverse(
    spot_token_addresses={
        base_weth.address,
        base_usdc.address,
        base_dino.address,
    }
)
latest_block = get_almost_latest_block_number(web3)
portfolio = vault.fetch_portfolio(universe, latest_block)
assert portfolio.get_position_count() == 3

uniswap_v2_quoter_v2 = UniswapV2Router02Quoter(uniswap_v2.router)

nav_calculator = NetAssetValueCalculator(
    web3,
    denomination_token=base_usdc,
    intermediary_tokens={base_weth.address},  # Allow DINO->WETH->USDC
    quoters={uniswap_v2_quoter_v2},
    debug=True,
)

routes = nav_calculator.create_route_diagnostics(portfolio)

print(routes)

Outputs:

# Routes and their sell values:

                      Asset                                     Address        Balance                   Router Works  Value
 Path
 USDC                  USDC  0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913           0.35                            yes   0.35
 WETH -> USDC          WETH  0x4200000000000000000000000000000000000006       0.000000  UniswapV2Router02Quoter   yes   0.00
 DINO -> USDC          DINO  0x85E90a5430AF45776548ADB82eE4cD9E33B08077  547942.000069  UniswapV2Router02Quoter    no      -
 DINO -> WETH -> USDC  DINO  0x85E90a5430AF45776548ADB82eE4cD9E33B08077  547942.000069  UniswapV2Router02Quoter   yes  36.69

Create a new NAV calculator.

Parameters
  • denomination_token

    Value the portfolio in this token.

    E.g. USDC

  • intermediary_tokens

    When looking for sell routes, these are allowed tokens we can do three leg trades.

    E.g. WETH, USDT.

  • quoters – Supported DEX quoters we can sell on.

  • block_identifier – Block number for the valuation time.

  • multicall

    Use multicall to optimise RPC access.

    None = autodetect.

    True = force.

    False = disabled.

  • multicall_gas_limit – Let’s not explode our RPC node

  • batch_size – Batch size to one Multicall RPC in the number of calls.

  • debug

    Unit test flag.

    Print out failed calldata to logging INFO, so you can inspect failed multicalls in Tenderly debugger.

__init__(web3, denomination_token, intermediary_tokens, quoters, multicall=None, block_identifier=None, multicall_gas_limit=10000000, debug=False, batch_size=15, legacy_multicall=False)

Create a new NAV calculator.

Parameters
  • denomination_token (Union[eth_typing.evm.HexAddress, eth_defi.token.TokenDetails]) –

    Value the portfolio in this token.

    E.g. USDC

  • intermediary_tokens (set[Union[eth_typing.evm.HexAddress, eth_defi.token.TokenDetails]]) –

    When looking for sell routes, these are allowed tokens we can do three leg trades.

    E.g. WETH, USDT.

  • quoters (set[eth_defi.vault.valuation.ValuationQuoter]) – Supported DEX quoters we can sell on.

  • block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) – Block number for the valuation time.

  • multicall (bool | None) –

    Use multicall to optimise RPC access.

    None = autodetect.

    True = force.

    False = disabled.

  • multicall_gas_limit – Let’s not explode our RPC node

  • batch_size – Batch size to one Multicall RPC in the number of calls.

  • debug

    Unit test flag.

    Print out failed calldata to logging INFO, so you can inspect failed multicalls in Tenderly debugger.

  • web3 (web3.main.Web3) –

generate_routes_for_router(router, portfolio, buy=False)

Create all potential routes we need to test to get quotes for a single asset.

Parameters
Return type

Iterable[eth_defi.vault.valuation.Route]

calculate_market_sell_nav(portfolio, allow_failed_routing=False)

Calculate net asset value for each position.

  • Portfolio net asset value is the sum of positions

  • What is our NAV if we do market sell on DEXes for the whole portfolio now

  • Price impact included

Parameters
Returns

Map of token address -> valuation in denomiation token

Return type

eth_defi.vault.valuation.PortfolioValuation

resolve_best_valuations(input_tokens, routes)

Any source token may have multiple paths. Pick one that gives the best amount out.

Parameters
do_multicall(calls)

Multicall mess untangling.

Parameters

calls (list[eth_defi.event_reader.multicall_batcher.MulticallWrapper]) –

fetch_onchain_valuations(routes, portfolio, legacy=False)

Use multicall to make calls to all of our quoters.

  • Does not handle reserve currency, as this never has any route to itself

Returns

Map routes -> amount out token amounts with this route

Parameters
Return type

dict[eth_defi.vault.valuation.Route, decimal.Decimal]

try_swap_paths(routes, portfolio)

Use multicall to try all possible swap paths for tokens.

  • Find the best buy options

  • Assume VaultPortfolio.spot_erc20 contains token amounts we want to buy

Returns

Map routes -> amount out token amounts with this route

Parameters
Return type

dict[eth_defi.vault.valuation.Route, decimal.Decimal]

create_route_diagnostics(portfolio)

Create a route diagnotics table.

  • Show all routes generated for the portfolio

  • Flag routes that work

  • Show values of each portfolio position if sold with the route

Outputs:

                     Asset                                     Address        Balance                   Router Works  Value
Path
USDC                  USDC  0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913           0.35                            yes   0.35
WETH -> USDC          WETH  0x4200000000000000000000000000000000000006       0.000000  UniswapV2Router02Quoter   yes   0.00
DINO -> USDC          DINO  0x85E90a5430AF45776548ADB82eE4cD9E33B08077  547942.000069  UniswapV2Router02Quoter    no      -
DINO -> WETH -> USDC  DINO  0x85E90a5430AF45776548ADB82eE4cD9E33B08077  547942.000069  UniswapV2Router02Quoter   yes  36.69
Returns

Human-readable DataFrame.

Indexed by asset.

Parameters

portfolio (eth_defi.vault.base.VaultPortfolio) –

Return type

pandas.core.frame.DataFrame

find_swap_routes(portfolio, buy=True)

Find the best routes to buy tokens.

Parameters

portfolio (eth_defi.vault.base.VaultPortfolio) –

Return type

eth_defi.vault.valuation.SwapMatrix