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.
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
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
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:
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:
110
10
100
50
90
30
and after the order is executed we will receive:
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:
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
truefor market order;post_only is always
falsefor market order;transfer_executed_tokens is always
true;
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 tokenY;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