-
Notifications
You must be signed in to change notification settings - Fork 110
feat(LR): add buy action, bid order and LR_1 support to find_best_quote RPC #2545
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
base: dev
Are you sure you want to change the base?
Conversation
|
@dimxy can you please supply request examples of |
The full path for this method is How It WorksThe method analyzes a list of available P2P orders and determines the most economical path, which could be:
It calculates the User Scenario: Trading Across Different Liquidity PoolsScenarioA user, Alex, wants to buy 1 BTC. He holds WETH on the Ethereum network. He checks the Komodo DeFi Framework orderbook and finds two promising orders from sellers:
Alex has WETH, not DAI or USDC. Manually, he would have to go to an external exchange, sell his WETH for DAI or USDC (paying fees), withdraw the tokens, and then place the P2P order. This is slow, complex, and error-prone. Solution using the APIAlex's application can use the
What find_best_quote Does
The ResponseThe RPC returns a single, optimal path. For instance, if the WETH → DAI route is cheaper, the response will detail the two required steps:
Alex's application can then present this unified quote to him. If he accepts, the application would use the response data to call Use Cases and ExamplesHere are examples for different scenarios that Use Case 1: Direct Atomic Swap (No LR Needed)This is the simplest case where the user already holds the required asset. Request: The user wants to buy 1 BTC and already has DAI. The provided order is a BTC-DAI ask order. NOTE:
.. should be flattened (removed the level of aggregation of orders by the "DAI" ticker) and sent in the Same should be done for bids.
{
"user_base": "BTC",
"user_rel": "DAI",
"method": "buy",
"volume": "1",
"asks": [
{
"base": "BTC",
"orders": [
{
"coin": "DAI",
"address": {
"address_type": "Transparent",
"address_data": "0x123...abc"
},
"price": "35000",
"pubkey": "maker_pubkey",
"uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"is_mine": false,
"base_max_volume": "1.5",
"base_min_volume": "0.1",
"rel_max_volume": "52500",
"rel_min_volume": "3500",
"conf_settings": null
}
]
}
],
"bids": []
}Response: No LR is needed, so {
"lr_data_0": null,
"lr_data_1": null,
"atomic_swap": {
"volume": "1",
"base": "DAI",
"rel": "BTC",
"price": "35000",
"method": "sell",
"order_uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"match_by": null,
"order_type": null
},
"total_price": "35000"
}Use Case 2: LR Before Atomic Swap (LR_0)This matches our user scenario. The user has one token ( Request: The user wants to buy 1 BTC with WETH. The best order requires DAI. {
"user_base": "BTC",
"user_rel": "WETH-ERC20",
"method": "buy",
"volume": "1",
"asks": [
{
"base": "BTC",
"orders": [
{
"coin": "DAI",
"address": {
"address_type": "Transparent",
"address_data": "0x123...abc"
},
"price": "35000",
"pubkey": "maker_pubkey",
"uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"is_mine": false,
"base_max_volume": "1.5",
"base_min_volume": "0.1",
"rel_max_volume": "52500",
"rel_min_volume": "3500",
"conf_settings": null
}
]
}
],
"bids": []
}Response: The response includes details for the WETH → DAI swap in {
"lr_data_0": {
"src_amount": "12.52",
"dst_amount": {
"amount": "35000.123"
},
"src_token": {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH",
"name": "Wrapped Ether",
"decimals": 18,
"symbol_kdf": "WETH-ERC20"
},
"dst_token": {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"symbol": "DAI",
"name": "Dai Stablecoin",
"decimals": 18,
"symbol_kdf": "DAI"
},
"tx": null,
"gas": 150000
},
"lr_data_1": null,
"atomic_swap": {
"volume": "35000.123",
"base": "DAI",
"rel": "BTC",
"price": "35000",
"method": "sell",
"order_uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"match_by": null,
"order_type": null
},
"total_price": "12.52"
}Use Case 3: LR After Atomic Swap (LR_1)The user has the token required for the P2P swap but wants the final asset to be a different token. Request: The user wants to sell 1 BTC and ultimately receive USDC. The best available order is from a buyer who will pay in DAI. {
"user_base": "BTC",
"user_rel": "USDC",
"method": "sell",
"volume": "1",
"asks": [],
"bids": [
{
"rel": "BTC",
"orders": [
{
"coin": "DAI",
"address": {
"address_type": "Transparent",
"address_data": "0x123...abc"
},
"price": "0.00002857",
"pubkey": "maker_pubkey",
"uuid": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"is_mine": false,
"base_max_volume": "52500",
"base_min_volume": "3500",
"rel_max_volume": "1.5",
"rel_min_volume": "0.1",
"conf_settings": null
}
]
}
]
}Response: The response includes details for the DAI → USDC swap in {
"lr_data_0": null,
"lr_data_1": {
"src_amount": "35000",
"dst_amount": {
"amount": "34995"
},
"src_token": {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"symbol": "DAI",
"name": "Dai Stablecoin",
"decimals": 18,
"symbol_kdf": "DAI"
},
"dst_token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"name": "USD Coin",
"decimals": 6,
"symbol_kdf": "USDC"
},
"tx": null,
"gas": 120000
},
"atomic_swap": {
"volume": "1",
"base": "BTC",
"rel": "DAI",
"price": "0.00002857",
"method": "sell",
"order_uuid": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"match_by": null,
"order_type": null
},
"total_price": "34995"
}Use Case 4: Cross-Chain Swap with LR Before and After (LR_0 and LR_1)This is the most advanced use case, demonstrating how liquidity routing can be used to bridge assets across two different EVM-compatible blockchains. The P2P atomic swap acts as the trustless bridge between the two ecosystems. Scenario: Alex can use the
Request: Alex wants to sell 5 WETH on Polygon and wants to know how many JOE tokens on Avalanche he will receive by routing through the MATIC/AVAX P2P order. {
"user_base": "WETH-ERC20",
"user_rel": "JOE-ERC20",
"method": "sell",
"volume": "5",
"asks": [
{
"base": "AVAX",
"orders": [
{
"coin": "MATIC",
"address": {
"address_type": "Transparent",
"address_data": "0x123...abc"
},
"price": "500",
"pubkey": "maker_pubkey_on_avax",
"uuid": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8g",
"is_mine": false,
"base_max_volume": "100",
"base_min_volume": "10",
"rel_max_volume": "50000",
"rel_min_volume": "5000",
"conf_settings": null
}
]
}
],
"bids": []
}Response: The response will detail all three steps of the cross-chain swap. {
"lr_data_0": {
"src_amount": "5",
"dst_amount": {
"amount": "21428.5"
},
"src_token": {
"address": "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619",
"symbol": "WETH",
"name": "Wrapped Ether",
"decimals": 18,
"symbol_kdf": "WETH-ERC20"
},
"dst_token": {
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"symbol": "MATIC",
"name": "Matic",
"decimals": 18,
"symbol_kdf": "MATIC"
},
"tx": null,
"gas": 145000
},
"atomic_swap": {
"volume": "21428.5",
"base": "MATIC",
"rel": "AVAX",
"price": "500",
"method": "sell",
"order_uuid": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8g",
"match_by": null,
"order_type": null
},
"lr_data_1": {
"src_amount": "42.857",
"dst_amount": {
"amount": "2995.5"
},
"src_token": {
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"symbol": "AVAX",
"name": "Avalanche",
"decimals": 18,
"symbol_kdf": "AVAX"
},
"dst_token": {
"address": "0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd",
"symbol": "JOE",
"name": "JOE",
"decimals": 18,
"symbol_kdf": "JOE-ERC20"
},
"tx": null,
"gas": 155000
},
"total_price": "599.1"
}Conceptual Use Case: Bridging EVM and Non-EVM ChainsWhile the current implementation focuses on 1inch for EVM-to-EVM liquidity routing, the underlying architecture is designed to be extensible. The Scenario: A future version of the
Setup Required for 1inch IntegrationTo use the liquidity routing features powered by 1inch, some initial setup is required in KDF. 1. KDF/MM2 Configuration (MM2.json)Your MM2 configuration file must contain the necessary information for the software to communicate with the 1inch API. API Endpoint URL: You need to specify the 1inch API endpoint, this can be provided through komodo proxy. The code reads this from the {
"1inch_api": "https://api.1inch.dev"
}API Key: The 1inch API requires an authentication key. The KDF codebase includes the logic to send this key as a Add Configuration for All Required Coins and tokens: Every token involved in the swap path must be enabled in your MM2 configuration. For the scenario above, your coins array would need to include configurations for WETH-ERC20, DAI, AAVE-ERC20, and USDC. MM2 uses this configuration to find contract addresses, decimals, and other essential on-chain information. Normally this is done by wallet developers. 2. Wallet and On-Chain SetupBefore executing a swap, the user's wallet must be prepared. Fund the Wallet:
Activate (enable) coins and tokens used in swaps with liquidity routing Handling allowance for tokens used in swaps In summary, a successful integration requires configuring the API endpoint and key, enabling all relevant coins in MM2, and ensuring the user's wallet is funded. |
|
@smk762 About this
|
shamardy
left a comment
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.
First review from my side! Will continue reviewing this while it's being tested. If any of the comments you want to fix in next PRs, it's fine, but we need a tracking issue for all these things.
| pub const fn eth_special_contract() -> &'static str { | ||
| ONE_INCH_ETH_SPECIAL_CONTRACT | ||
| } | ||
| } // TODO: must use the 1inch call, not a const (on zk chain it's not const) |
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.
We need a tracking issue for this
| /// The GUI should pick tokens which resides in the same chains with user_base and/or user_rel and query "best_orders" RPC (maybe multiple times). | ||
| /// The "best_orders" results are asks or bids which can be passed into this RPC. | ||
| /// | ||
| /// TODO: develop a more convenient RPC to find ask and bid orders for finding the best swap with LR. |
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.
Please open a tracking issue for all the todos in this PR.
| // TODO: implement later | ||
| // trade_fee: ... |
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.
I would say this is important for LR, is it implemented in 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.
Yes, have not implemented this but will certainly do in the next PR
| .into_iter() | ||
| .zip(prices_in_series) | ||
| .map(|((src, dst), series)| { | ||
| let dst_price = cross_prices_average(series); // estimate SRC/DST price as average from 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.
it seems that we still use the average price not the close 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.
Yes, I added a new cross_prices_close function in my feature branch.
But, in fact I had to switch back to the average and even had to extend the depth for price history. Otherwise swaps fail too often because the cross_prices call returns just an empty result. When I changed the depth from 1 to 10 I have not seen empty responses yet. (Maybe this is the "correlation does not equal causation" rule but we'll see.)
| let dex_fee_rate = DexFee::dex_fee_rate(&taker_ticker, &maker_ticker); | ||
| volume_with_fees / (MmNumber::from("1") + dex_fee_rate) // Deduct dex fee to get the atomic swap taker amount | ||
| } else { | ||
| user_sell_amount.clone() // TODO: use atomic_swap_taker_amount |
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.
Can you please explain this todo?
|
|
||
| /// Select the best swap path, by minimum of total swap price, including LR steps and atomic swap) | ||
| #[allow(clippy::result_large_err)] | ||
| async fn select_best_swap(self, ctx: &MmArc) -> MmResult<(LrSwapCandidateInfo, MmNumber), LrSwapError> { |
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.
Do all 1inch swaps use the same gas amount? I see that the ClassicSwapData fetched from the 1inch API provides an estimated gas cost, and this should be factored in, as a swap with a better price might have a higher gas cost, making it more expensive overall
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 gas limits returned from the find_best_quote are not used in the execute_routed_trade RPC as in the trade RPC a new 1inch request is made, to get the actual 1inch swap transaction details. The gas limit is used from this new request.
I edited #2545 (comment) and added my changes: |
…p and do it only in the test itself
* dev: fix build script failing to find .git/HEAD (#2601) refactor(EVM): rename fn, fix timeouts, add activation req validation (#2543) improvement(`static mut`s): `static mut` removal (#2590) fix(orders): set subscription on kickstart and skip GC of own pubkeys (#2597) fix(ordermatch): ignore loop-back; clear on null root; reject stale keep-alives (#2580) fix(clippy): fix clippy warnings for #2565 (#2589) fix(Trezor): fix utxo and eth calls due to firmware changes (#2565)
Please add any details to this issue (and DM me API keys etc) and I'll set it up KomodoPlatform/komodo-defi-proxy#28 |
Added a note to this PR description. |
|
@smk762 |
Next phase of liquidity routing (LR) development (WIP).
Fixes and new features to the
find_best_quoteRPC:Improved code, RPC params, fixed total price calculations.
Details for the find_best_quote RPC are here:
komodo-defi-framework/mm2src/mm2_main/src/rpc/lp_commands/lr_swap_api.rs
Line 18 in 342fb1b
This code requires setting up 1inch API service as an LR provider.
Setting Up 1inch Provider in KDF
The MM2.json should contain "1inch_api" param pointing to a url to the 1inch API server ("https://api.1inch.dev") or KDF proxy.
The KDF proxy must re-route requests to the 1inch API server and add an "Authorization" HTTP header set to "Bearer <1inch-API-KEY>" value.
@smk762