Liquidity Source Integration

Overview

This manual contains information necessary for integrating Hanji into external aggregators.

In general, the integration task involves:

  • obtaining information about the current state of order books;

  • simulating and calculating execution prices;

  • executing orders on-chain.

Currently, only two types of contracts are required for integration:

  • OnchainCLOB - order books (one contract per trading pair) or contracts that implement the same trading methods and proxy requests to order books;

  • Helper - a helper contract that allows you to read the order book's state.

Important Notes

Quantity & Price Scaling Factors

To optimize computation and data storage capacity, contracts use special scaling factors (multipliers) for token amounts and prices.

For example, if the scaling factor for a WETH (decimals = 18) token is 10^13, then when passing quantity = 1 to a contract, the actual value used will be quantity * scalingFactor, i.e., 1 * 10^13 = 10^13 WEI and 10^13 / 10^18 = 0.00001 ETH. For volumes, this parameter determines the minimum number of tokens that can be placed in the order book.

These multipliers can be obtained for specific tokens in a specific trading pair from the Hanji API or by calling the getConfig method on the order book contract:

function getConfig()
    external
    view
    returns (
        uint256 _scaling_factor_token_x, // scaling factor for token X in pair X/Y
        uint256 _scaling_factor_token_y, // scaling factor for token Y in pair X/Y
        address _token_x,
        address _token_y,
        bool _supports_native_eth,
        bool _is_token_x_weth,
        address _ask_trie,
        address _bid_trie,
        uint64 _admin_commission_rate,
        uint64 _total_aggressive_commission_rate,
        uint64 _total_passive_commission_rate,
        uint64 _passive_order_payout_rate,
        bool _should_invoke_on_trade
    );

Prices also have their own multiplier, which can be obtained from the Hanji API or calculated using a simple formula, knowing the decimals and scaling factors for both tokens in the pair. For example:

So, to place a WETH/USDC order with a price of 2000, you need to multiply the price by PriceScalingFactor = 10 to get a final contract price of 20000. It is also easy to see that the maximum price precision in this case will be equal to 1 decimal place.

circle-info

The scaling factor for the same token can be different depending on the specific pair, and even for another pair with exactly the same tokens. Scaling factors are set by the pool creators to achieve the best possible price precision for a given pair and to define the minimum usable amounts of tokens.

Price restrictions

For compactness, prices within a contract are compressed from uint72 to uint24 by encoding the mantissa and exponent. This imposes restrictions on the use of prices: a price can only have 6 significant digits:

In the vast majority of cases, 6 significant digits are sufficient; however, it should be kept in mind that in the event of a significant increase in the price of an asset, it is necessary to round off the least significant digits.

OrderBook

circle-info

In previous versions it was possible to obtain the full state of order books by listening to events from the node, but after the latest protocol update, this is no longer possible. The protocol still generates events, but they do not contain information about the virtual liquidity that is applied to a trade by the new quoting algorithm. Therefore, an order book built using events will not contain all price levels.

The order book state for backend calculations can be obtained using FastQuoterHelper contract and the assembleOrderbookFromOrders method:

Parameters

Parameter
Description

lobAddress

order book contract address

isAsk

true for ask levels, false for bids

maxPriceLevels

maximum number of price levels to obtain (max depth)

As a result, two arrays arrayPrices and arrayShares are returned, containing prices and volumes corresponding to these prices, sorted from best to worst.

For example, in the case of sell levels (asks), the following result may be returned:

Index
Price
Shares (Qty)

0

100000

100

1

100100

250

2

100400

30

If the order book is empty, empty arrays will be returned. If the number of records in the order book is greater than maxPriceLevels, then exactly maxPriceLevels records will be returned.

It's important to remember that the resulting values are scaled using the scaling factors for the volumes and prices. To obtain the actual values, divide the price by the PriceScalingFactor, multiply the volume by ScalingFactorX, and divide by 10^decimals:

Simulation

The Hanji order book operates exactly like a traditional centralized exchange order book and uses the Price-Time Priority model.

For example, consider a limit sell order with a price of 100 and a quantity of 100, and the following order book bids:

Price
Qty

110

10

100

50

90

30

and after the order is executed we will receive:

circle-info

It is important to note that the fee calculation is always performed in token Y.

You can also obtain the fee rates directly from the order book using the getConfig method:

For execution via external aggregators, a MARKET order is typically used, for which _total_aggressive_commission_rate and _passive_order_payout_rate are relevant.

The total fee rate is calculated as follows:

Then the total fee can be estimated as follows:

Execution

Hanji implements a classic order book model in which order quantities are always specified in token X. Therefore, swaps from token X to Y and from token Y to X are executed differently.

Knowing the token addresses and using the getConfig method, it is possible to determine which token is token X and which is token Y:

Swap X to Y

When selling token X in the X/Y pair, the user sends token X, and the fee will be deducted from the final amount received in token Y.

To sell token X, the order book’s placeOrder method is used:

Parameter
Description

isAsk

true for sell token X, false for buy token X (sell token Y)

quantity

scaled volume in token X

price

scaled price for limit order or min price for sell order by market or max price for buy order by market

max_commission

parameter is designed to be able to avoid some complex scenarios with frontrunner attacks, but can be set to const value type(uint128).max by default

market_only

true for markets orders, when unexecuted volume canceled, false for limit orders, when unexecuted volume placed to order book

post_only

If true, the order cannot be aggressively executed against other orders when placed and must be a passive limit order, for all other cases false

transfer_executed_tokens

If true, transfer the executed tokens directly to the user's address, otherwise leave them in the user's deposit on order book contract. Currently ignored by the contract and defaults to true

expires

The time in UTC seconds at which the transaction will expire. After this time, the transaction will be reverted

Based on the specifics of the orders used by aggregators, there are recommended values for some parameters:

  • isAsk is always true, as this method is only used for selling token X;

  • price, it is recommended to set this also for market orders to prevent too much slippage;

  • max_commission is always type(uint128).max;

  • market_only is always true for market order;

  • post_only is always false for market order;

  • transfer_executed_tokens is always true;

circle-info

Unexecuted volume is always returned back.

Swap Y to X

When selling token Y (buying X) in the X/Y pair, the fee is deducted from the input amount in token Y.

For the case of selling token Y using a market order, there is a special method:

The difference from placeOrder is that the volume is specified in token Y using the target_token_y_value parameter. The fee is deducted internally within the method, so there is no need for additional calculations to estimate the required amount of token Y for the conversion.

Based on the specifics of the orders used by aggregators, there are recommended values for some parameters:

  • isAsk is always false, as this method is only used for selling token Y;

  • price, it is recommended to set this also for market orders to prevent too much slippage;

  • max_commission is always type(uint128).max;

  • transfer_executed_tokens is always true.

Example: Uniswap V3-style proxy

As an example, we will provide an implementation of a proxy contract with an interface in the Uniswap V3 router style:

For market sell orders, you can set the lowest possible price to guarantee a match. For market buy orders, you can set the highest possible price:

For compatibility with Uniswap V3 API and the ability to specify minimum/maximum prices for an order, the example below implements the _calcLobPrice internal function, which, in the case of a non-zero sqrtPriceLimitX96 variable, converts the price from the Uniswap V3 format to a price compatible with Hanji.

We also provide below the code of some of the used internal functions, modifiers and constructor for completeness:

An internal method that converts the price from Uniswap V3 format to Hanji format:

and the IOnchainCLOB interface:

Last updated