Open Dominik1999 opened 1 month ago
We don't need to focus too much on UX. It is mostly a showcase for Composability Labs on how to build a simple order book.
In my mind, a user would simply type the order type buy/sell
and the amount (and price) 50 Asset1 to 1000 Asset2
.
So in the CLI, I would expect:
order
as the command to create a new orderbuy
or sell
as the type of the order<amount 1> <faucet id>
as the first leg of the trade<amount 2> <faucet id>
as the second leg of the tradeAn example of a full command would be order buy 50 0xa0e61d8a3f8b50be 1000 0xb1d43d1h5d6z46gj
Ideally, we would replace the <fauvet ud>
with a shorter "unique" identifier like POL
- at least for the showcase.
@phklive for the matching algorithm:
So the user should by able to type the CLI command: buy X ETH for Y BTC
.
Then the program starts creating a sub-set of the sorted ETH order book (by price). Assuming that the order book is sorted ascending, so the cheapest price is on top, the order book looks like this
| Note ID | Requested Asset | Amount Requested | Containing Asset | Amount Containing | Price |
|------------|-----------------|------------------|------------------|-------------------|---------|
| 0x7y8z9a | ETH | 199 | BTC | 10 | 19.90 |
| 0x1o2p3q | ETH | 786 | BTC | 20 | 39.30 |
| 0x4x5y6z | ETH | 814 | BTC | 20 | 40.70 |
| 0x4q5r6s | ETH | 320 | BTC | 8 | 40.00 |
| 0x1m2n3o | ETH | 585 | BTC | 14 | 41.79 |
| 0x7r8s9t | ETH | 720 | BTC | 17 | 42.35 |
| 0x1w2x3y | ETH | 127 | BTC | 3 | 42.33 |
| 0x7t8u9v | ETH | 590 | BTC | 13 | 45.38 |
| 0x7g8h9i | ETH | 965 | BTC | 21 | 45.95 |
| 0x4y5z6a | ETH | 992 | BTC | 22 | 45.09 |
| 0x1l2m3n | ETH | 394 | BTC | 8 | 49.25 |
| 0x7l8m9n | ETH | 523 | BTC | 11 | 47.55 |
| 0x4r5s6t | ETH | 619 | BTC | 14 | 44.21 |
| 0x1n2o3p | ETH | 762 | BTC | 17 | 44.82 |
| 0x4w5x6y | ETH | 671 | BTC | 15 | 44.73 |
| 0x4d5e6f | ETH | 312 | BTC | 7 | 44.57 |
| 0x4o5p6q | ETH | 275 | BTC | 6 | 45.83 |
| 0x7p8q9r | ETH | 234 | BTC | 3 | 78.00 |
| 0x7s8t9u | ETH | 369 | BTC | 5 | 73.80 |
| 0x1s2t3u | ETH | 405 | BTC | 9 | 45.00 |
| 0x4v5w6x | ETH | 582 | BTC | 19 | 30.63 |
| 0x4e5f6g | ETH | 150 | BTC | 5 | 30.00 |
| 0x7h8i9j | ETH | 926 | BTC | 16 | 57.88 |
| 0x1f2g3h | ETH | 800 | BTC | 14 | 57.14 |
| 0x1k2l3m | ETH | 354 | BTC | 6 | 59.00 |
| 0x7j8k9l | ETH | 763 | BTC | 15 | 50.87 |
| 0x1b2c3d | ETH | 633 | BTC | 12 | 52.75 |
| 0x7a8b9c | ETH | 151 | BTC | 3 | 50.33 |
| 0x1d2e3f | ETH | 972 | BTC | 19 | 51.16 |
| 0x1u2v3w | ETH | 509 | BTC | 9 | 56.56 |
| 0x4f5g6h | ETH | 984 | BTC | 18 | 54.67 |
| 0x1d2e4f | ETH | 523 | BTC | 10 | 51.30 |
| 0x7b8c9d | ETH | 991 | BTC | 18 | 55.06 |
| 0x4z5a6b | ETH | 991 | BTC | 18 | 55.06 |
| 0x1l3m5o | ETH | 750 | BTC | 21 | 35.71 |
| 0x7r3p7m | ETH | 850 | BTC | 29 | 31.45 |
| 0x1e3f3t | ETH | 120 | BTC | 17 | 12.50 |
|
So our simple matching algorithm starts on the top of the list and sees if it can fulfill the order for the requested price or cheaper.
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct Order {
note_id: String,
amount_requested: f64, // ETH
amount_contained: f64, // BTC
price: f64, // Price = ETH/BTC
}
impl Order {
fn new(note_id: &str, amount_requested: f64, amount_contained: f64) -> Self {
let price = amount_requested / amount_contained;
Order {
note_id: note_id.to_string(),
amount_requested,
amount_contained,
price,
}
}
}
fn match_order(order_book: &[Order], buy_eth: f64, btc_offered: f64) -> Result<(Vec<String>, f64), bool> {
let mut remaining_eth = buy_eth;
let mut total_btc_needed = 0.0;
let mut matched_orders: Vec<String> = Vec::new();
let mut total_price = 0.0;
// Sort the order book by price in ascending order
let mut sorted_orders = order_book.to_vec();
sorted_orders.sort_by(|a, b| a.price.partial_cmp(&b.price).unwrap());
for order in sorted_orders.iter() {
if remaining_eth <= 0.0 {
break;
}
let eth_to_buy = remaining_eth.min(order.amount_requested);
let btc_needed = eth_to_buy / order.price;
if total_btc_needed + btc_needed > btc_offered {
return Err(false);
}
remaining_eth -= eth_to_buy;
total_btc_needed += btc_needed;
total_price += order.price * eth_to_buy;
matched_orders.push(order.note_id.clone());
}
if remaining_eth > 0.0 {
Err(false) // Not enough orders to fulfill the request
} else {
let average_price = total_price / buy_eth;
Ok((matched_orders, average_price))
}
}
fn main() {
// Sample order book
let order_book = vec![
Order::new("0x1a2b3c", 100.0, 2.0),
Order::new("0x1d2e3f", 200.0, 4.0),
Order::new("0x1g2h3i", 150.0, 3.5),
Order::new("0x1j2k3l", 50.0, 1.0),
];
// Example: Buying 250 ETH for 7 BTC
let buy_eth = 250.0;
let btc_offered = 7.0;
match match_order(&order_book, buy_eth, btc_offered) {
Ok((matched_orders, avg_price)) => {
println!("Order fulfilled with NoteIDs: {:?} at an average price of {:.2}", matched_orders, avg_price);
},
Err(_) => {
println!("Order cannot be fulfilled.");
}
}
}
I created this with ChatGTP and reviewed, it looks correct. You probably need u64
instead of f64
.
SWAPp note currently works and has basic tests in this repository here: https://github.com/compolabs/spark-miden-v1/tree/main/tests/mock_integration
The CLI currently computes the expected P2ID of the SWAP note before it is consumed. This is fine, but will only work for the standard SWAP note, not the partially fillable SWAP note.
Example: User 1 is selling 10 tokens A for 10 tokens B. We don't know how many tokens A User 2 wants in the future. When User 2 builds the transaction, "I want 5 tokens A", then we will know the output of the SWAPp note.
Right now, this is a peer to peer order book. This is fine for the first PoC version, but in the future, we will need to make some sort of "clearing house" or "matcher" contract that can consume multiple SWAPp orders and fill them.
Let's change the computation of the expected P2ID then. Let's work in parallel. @phklive can make it work end-to-end with the SWAP note. @part can start integrating the SWAPp note, first in the order book creation.
When we integrate the SWAPp note, we must adjust the matching algorithm as well.
Let's build a CLI-based order book
Do some research and tests first - Scripts to set up the order book
ASSET1
ASSET2
TAG
but different inputs. For the first version: use the normalSWAP
script.SWAPp
notes insteadThe program - Wrapper around the Miden client with a CLI
TAG
in the CLI. The program then queries the Miden Node and gets all public notes with the sameTAG
. As a result, all notes should be listed in the interface.(requested_asset/asset)
.buy 10 ETH for 1 BTC
cannot be fulfilled, so a new order will be added to the BTC/ETH order booksell 1 BTC for 10 ETH
. This will always result in a transaction.TransactionRequest
, since it is not a standard scriptCollect feedback