-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(swap): rpc to find best swap with liquidity routing for ask #2362
base: dev
Are you sure you want to change the base?
Conversation
* dev: fix(derive_key_from_path): check length of current_key_material (#2356) chore(release): bump mm2 version to 2.4.0-beta (#2346) fix(tests): add additional testnet sepolia nodes to test code (#2358) fix(swaps): maintain legacy compatibility for negotiation messages (#2353) refactor(SwapOps): impl defaults for protocol specific swapops fns (#2354) feat(tpu-v2): provide swap protocol versioning (#2324) feat(wallet): add change mnemonic password rpc (#2317) fix(tpu-v2): fix tpu-v2 wait for payment spend and extract secret (#2261) feat(tendermint): unstaking/undelegation (#2330) fix(utxo-withdraw): get hw ctx only when `PrivKeyPolicy` is trezor (#2333) feat(event-streaming): API-driven subscription management (#2172) fix(hash-types): remove panic, enforce fixed-size arrays (#2279) fix(ARRR): store unconfirmed change output (#2276) feat(tendermint): staking/delegation (#2322) chore(deps): `timed-map` migration (#2247) fix(mem-leak): `running_swap` never shrinks (#2301) chore(dep-bump): libp2p (#2326) refactor(build script): rewrite the main build script (#2319)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! esp for the excessive comments :)
this was a light review to grasp new thigns. nothing is blocking from my side.
will read the SoW for more context and review again next review week if this is still unmerged.
@@ -0,0 +1,263 @@ | |||
//? RPC implementations for swaps with liquidity routing (LR) of EVM tokens |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
module docs start like this: //!
also other instances
/// TODO: currently supported only ask orders with rel=token_x, with routing User's my_token into token_x before the dex-swap. | ||
/// The RPC should also support: | ||
/// bid orders with rel=token_x, with routing token_x into my_token after the dex-swap | ||
/// ask orders with base=token_x, with routing token_x into my_token after the dex-swap | ||
/// bid orders with base=token_x, with routing User's my_token into token_x before the dex-swap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't ask with rel=token_x
=== bid with base=token_x
, can't we always consider everything as a bid
for simplicity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we can indeed.
I am going to add support for all cases in the next PR
base: base_ticker, | ||
amount: "0.123".into(), | ||
asks, | ||
my_token: weth_ticker, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can use rel
instead of my_token
? makes sense since we have base
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'll need to refactor ticker params in the next PR where whole set of bid/asks options are to be added
let src_contract = eth_addr_to_hex( | ||
lr_data | ||
.src_contract | ||
.as_ref() | ||
.ok_or(ApiIntegrationRpcError::InternalError("no contract".to_owned()))?, | ||
); | ||
let dst_contract = eth_addr_to_hex( | ||
lr_data | ||
.dst_contract | ||
.as_ref() | ||
.ok_or(ApiIntegrationRpcError::InternalError("no contract".to_owned()))?, | ||
); | ||
let chain_id = lr_data | ||
.chain_id | ||
.ok_or(ApiIntegrationRpcError::InternalError("no chain id".to_owned()))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we have these fields optionalized but then error whenever one of them is None while doing this calculation. what about having them non-optional and initialized right inside new()
.map(|((src, dst), series)| { | ||
let dst_price = cross_prices_average(series); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: this series is a list of all prices on 1inch ha? should we average them or pick the best x prices that will fill our volume?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is prices from 1inch.
Maybe we should use best prices, I think, tests will show.
This price is obtained to estimate how much source token amount we need to fill the swap when 1inch as a liquidity routing provider (and the actual LR conversion is done for the best quote price).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments from the wip review. Only this comment #2362 (comment) needs changes in this PR, other comments are for future PRs and discussion.
mm2src/coins/eth.rs
Outdated
/// Find a EVM token name in the coins file by a given contract address. | ||
/// If contract_addr is empty the function returns the platform coin name | ||
pub async fn find_token_by_address(ctx: &MmArc, chain_id: u64, contract_addr: Option<EthAddress>) -> Option<String> { | ||
let coin_ctx = CoinsContext::from_ctx(ctx).unwrap(); | ||
let coins = coin_ctx.coins.lock().await; | ||
coins | ||
.iter() | ||
.find(|(_ticker, coin_struct)| { | ||
if let MmCoinEnum::EthCoin(eth_coin) = &coin_struct.inner { | ||
match eth_coin.coin_type { | ||
EthCoinType::Erc20 { token_addr, .. } => { | ||
if let Some(contract_addr) = contract_addr { | ||
if token_addr == contract_addr && eth_coin.chain_id() == chain_id { | ||
return true; | ||
} | ||
} | ||
}, | ||
EthCoinType::Eth => { | ||
if contract_addr.is_none() && eth_coin.chain_id() == chain_id { | ||
return true; | ||
} | ||
}, | ||
EthCoinType::Nft { .. } => {}, | ||
} | ||
} | ||
false | ||
}) | ||
.map(|(ticker, _)| ticker.clone()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this function a bit similar to this
komodo-defi-framework/mm2src/coins/eth/erc20.rs
Lines 93 to 107 in 22719c0
/// Finds an enabled ERC20 token by its contract address and returns it as `MmCoinEnum`. | |
pub async fn get_enabled_erc20_by_contract( | |
ctx: &MmArc, | |
contract_address: Address, | |
) -> MmResult<Option<MmCoinEnum>, String> { | |
let cctx = CoinsContext::from_ctx(ctx)?; | |
let coins = cctx.coins.lock().await; | |
Ok(coins.values().find_map(|coin| match &coin.inner { | |
MmCoinEnum::EthCoin(eth_coin) if eth_coin.erc20_token_address() == Some(contract_address) => { | |
Some(coin.inner.clone()) | |
}, | |
_ => None, | |
})) | |
} |
I see that I didn't add
chain_id
to get_enabled_erc20_by_contract
which is a mistake. We should have one function, please merge those in the next PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, my function searches in the enabled coins, but for LR tokens it is not necessary to have them enabled, I guess.
So for LR tokens info it's better to look into the coins file directly.
Ah, sorry, you mentioned the get_enabled_erc20_by_contract fn (which searches in enabled tokens) but I actually began to look into another fn get_erc20_ticker_by_contract_address, which searches in the coins file and it is almost what I needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is my proposed change c850505 to reuse the get_erc20_ticker_by_contract_address fn (instead of find_token_by_address fn).
Also I'll add chain_id to get_enabled_erc20_by_contract in a dedicated PR.
@@ -72,6 +72,7 @@ impl TryFromCoinProtocol for Erc20Protocol { | |||
contract_address, | |||
} => { | |||
let token_addr = valid_addr_from_str(&contract_address).map_err(|_| CoinProtocol::ERC20 { | |||
// TODO: maybe add error description to this err (we're losing 'Invalid address checksum' here) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Should have a separate issue for this.
"lr_best_quote" => handle_mmrpc(ctx, request, lr_best_quote_rpc).await, | ||
"lr_quotes_for_tokens" => handle_mmrpc(ctx, request, lr_quotes_for_tokens_rpc).await, | ||
"lr_fill_order" => handle_mmrpc(ctx, request, lr_fill_order_rpc).await, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding those to a new namespace, preview
/experimental
or test. I like preview
most.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, we might be asked to combine multiple functionalities in an aggregator RPC, so asks are chosen automatically from ordebook and fed to best quote. This #1609 can help a lot in liquidity routing too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, we might be asked to combine multiple functionalities in an aggregator RPC
I thought about an extra RPC to find most suitable (liquid) tokens for LR (with the orderbook as a source for tokens) - added a TODO to the PR description. Ofc maybe this could be part of an aggregator RPC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added preview:: prefix to the lr_.. rpcs c850505
/// bid orders with rel=token_x, with routing token_x into my_token after the dex-swap | ||
/// ask orders with base=token_x, with routing token_x into my_token after the dex-swap | ||
/// bid orders with base=token_x, with routing User's my_token into token_x before the dex-swap | ||
pub async fn lr_best_quote_rpc( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to get streamed events for the best quote in the future? I see that only fusion+ has a websocket API so it's probably not possible. But we can stream it periodically I guess instead of GUI polling it. Please add this to the issue checklist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A somewhat related comment. Price changes are very fast, so we would have to stream it every second at most, we can make it configurable, but this is where slippage comes in when actually filling the order as an error can be returned if execution price is different from the one shown to the user (the price minus slippage of course).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OHLC API returns token prices with granularity of 5 minutes minimum.
Getting a quote each second ver 1inch API I guess would cost much their infra fees (unless we setup our own service which can interact with their contract directly, with the needed interval).
Added a TODO about streaming to this PR description.
/// API params builder to get OHLC price history for token pair | ||
/// See 1inch docs: https://portal.1inch.dev/documentation/apis/portfolio/swagger?method=get&path=%2Fintegrations%2Fprices%2Fv1%2Ftime_range%2Fcross_prices | ||
#[derive(Default)] | ||
pub struct CrossPriceParams { | ||
chain_id: u64, | ||
/// Base token address | ||
token0_address: String, | ||
/// Quote token address | ||
token1_address: String, | ||
/// Returned time series intervals | ||
granularity: Option<DataGranularity>, | ||
/// max number of time series | ||
limit: Option<u32>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we need OHLC data, why not use spot price API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spot price returns a token price in fiat currency.
The OHLC API returns prices directly for token pairs we needed, I thought this is more accurate.
@dimxy please merge with dev for the CI fixes |
…ct_address fn, add new get_platform_ticker fn
* dev: feat(tendermint): claim delegation rewards (#2351) fix(eth-tpu): remove state from funding validation (#2334) improvement(rpc-server): rpc server dynamic port allocation (#2342) fix(tests): fix or ignore unstable tests (#2365) fix(fs): make `filter_files_by_extension` return only files (#2364)
Initial code for liquidity routing (LR) support for KDF.
The added RPC finds best (most price efficient) swap path from a list of provided ask orders, to fill them with a User token converting into orders' tokens with an interim LR-swap.
This PR covers only finding the best quote for one case (the ask order when 'rel' is token). Next steps are adding/fixing RPCs to get quotes for order types (bids) and RPCs to run swaps with LR.
TODO:
TODO to research: