Skip to content
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: quotes-server #430

Closed
wants to merge 37 commits into from
Closed

feat: quotes-server #430

wants to merge 37 commits into from

Conversation

bodymindarts
Copy link
Member

No description provided.


message GetQuoteToBuyCentsRequest {
oneof quote_for {
uint64 amount_to_sell_in_sats = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one might want to "sell $100 worth of bitcoin", so the amount as an input should be dollar, not sats.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s what the next line is for. You can specify either the btc amount to sell or the USD amount to buy (your scenario). See the oneof keyword

Copy link
Member

@nicolasburtey nicolasburtey Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a different use case. yours is buy BTC with a USD amount. the one I mention is sell BTC with a USD amount

Copy link
Member Author

@bodymindarts bodymindarts Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a matrix of 2x2x2 = 8 use cases.

The 3 binary variables are:
buy / sell USD
denominate amount in BTC / USB
execute immediately / execute in future

The api of the current price server has each of the 8 use cases as its own function call.
In this api I'm proposing to reduce the number of exposed endpoint to 2 (buy / sell) and pass the other binaries as function variables (denomination / execution).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If as a user, I request to sell $100 of bitcoin, but get credited 99.98 because of some math approximation and because if a mismatch of source of truth, then I would think there are hidden fees in here or that the system doesn't really work well.

If stablesats is going to be the source of truth, we can make those shortcuts because it would worsen the user experience and reduce trust in the system

Copy link
Member Author

@bodymindarts bodymindarts Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in your example the request would call GetQuoteToBuyCents(GetQuoteToBuyCentsRequest) because we are buying USD (ie. selling bitcoin). But we want to denominate the amount in usd ($100 worth). That means in the oneof argument I would specify the amount_to_buy_in_cents = 100

message GetQuoteToBuyCentsRequest {
  oneof quote_for {
    uint64 amount_to_sell_in_sats = 1;
    uint64 amount_to_buy_in_cents = 2;
  }

  bool immediate_execution = 3;
}
=>
GetQUoteToBuyCentsRequest {
  quote_for: { amount_to_buy_in_cents: 100 }
  immediate_execution: false
}

This is the same way it works now with the GetSatsFromCentsForFutureBuy call.

The only difference here is that we are passing some arguments instead of separating each method call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I misread the initial interface.

If I want to buy $100 of bitcoin, can I get a quote by passing a dollar amount?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes (see example above)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

I think message GetQuoteToBuyCentsRequest

would be better worded withmessage GetQuoteToBuyDollarRequest or message GetQuoteToBuyStablesatsRequest

because otherwise it's a bit convoluted that we using cents as both the assets and unit of account.

This may be why I have been mis interpreting the API (I skimed through it and didnt' try to read it thoroughly)

@@ -4,6 +4,7 @@ members = [
"shared",
"ledger",
"price-server",
"quotes-server",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to be different than the price server?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easier to immutably replace rather than try to merge legacy with new server IMO

rpc AcceptQuote(AcceptQuoteRequest) returns (AcceptQuoteResponse) {}
}

message GetQuoteToBuyCentsRequest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we don't support this as yet, but do we also want to be more specific about the currency we're dealing with here, or more generic about the unit somehow?

For e.g. if we decided to add Euros to stablesats in the future somehow, a quote could be for buying EurCents or UsdCents. There's also the possibility that we use a non-cent currency like GBP (penny) or JPY (no minor unit).

We can pivot on the currency codes instead and think about some way to genericize the minor unit we're using? E.g.:

  • GetQuoteToBuyUsdRequest with a base and an offset for the request params / response fields?

Not sure if this is premature optimization though?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Premature IMO.

@vindard
Copy link
Contributor

vindard commented Sep 15, 2023

Another general comment. I have the problem right now where it's not easy to reason about whether I'm using the right quote type for the right circumstance. I'd have to refer back to this test case to see how the different prices behave with spreads, and the implications for using the wrong method are that we open ourselves up to arbitrage attacks.

I like that with these changes we explicitly have only 2 options, BuyCents or SellCents, which suggests that with a buy the spread should always be in one direction and with a sell in the other direction. I'm wondering if there's a way to capture this spread behaviour more explictly in the new api still. Maybe we can do this by returning the spread applied (amount, and whether +'ve or -'ve) in the response as an option?

Comment on lines 5 to 8
direction direction_enum NOT NULL,
immediate_execution BOOLEAN NOT NULL,
sat_amount BIGINT NOT NULL,
cent_amount BIGINT NOT NULL,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these need to be indexed?

@@ -3,6 +3,7 @@ use derive_builder::Builder;
use serde::{Deserialize, Serialize};

use crate::currency::*;
use crate::EntityEvents;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be exposed in top level. Also use crate::{..}

.price_calculator
.cents_from_sats_for_buy(Satoshis::from(sats), immediate_execution)
.await?;
let new_quote = NewQuote::builder()
.direction(Direction::BuyCents)
.immediate_execution(immediate_execution)
.cent_amount(usd_amount)
.sat_amount(Satoshis::from(sats))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant Satoshis::from.. just set it to a local var the same you use line 77.

@@ -2,10 +2,13 @@ use thiserror::Error;

use shared::time::*;

use crate::cache::OrderBookCacheError;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline missing?

let quote = app
.quote_cents_from_sats_for_buy(dec!(100_000_000), false)
.await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not 2? And magic number...

.await
}

pub async fn quotes_btc_liability(&self) -> Result<Option<AccountBalance>, LedgerError> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btc is not a liability

}

fn default_expiration_interval() -> Duration {
const DEFAULT_EXPIRATION_INTERVAL: std::time::Duration = std::time::Duration::from_secs(120);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant const. default... fn already identifies unique access point + its not shared outside of this file or even outside this function.

.expect("Could not build quote");
let quote = self.quotes.create(new_quote).await?;
if immediate_execution {
self.accept_quote(quote.id).await?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be in transaction

.direction(Direction::BuyCents)
.immediate_execution(immediate_execution)
.cent_amount(usd_amount)
.sat_amount(sats)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing cent_spread / sat_spread

.expect("Could not build quote");
let quote = self.quotes.create(new_quote).await?;
if immediate_execution {
self.accept_quote(quote.id).await?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to be in transactoi

.expect("Could not build quote");
let quote = self.quotes.create(new_quote).await?;
if immediate_execution {
self.accept_quote(quote.id).await?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accept_quote(id) ->
accept_quote_in_tx(quote, tx)

return Err(QuotesAppError::QuoteExpired(id.to_string()));
}

quote.accept();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accept should fail with expired / already accepted errors.

)
.await?;
}
self.quotes.update(quote).await?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not atomic?

@@ -124,3 +124,58 @@ macro_rules! decimal_wrapper_common {
}
};
}

#[macro_export]
macro_rules! entity_id {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant

@thevaibhav-dixit thevaibhav-dixit force-pushed the quotes branch 3 times, most recently from 24c3f06 to 3dc60d9 Compare November 6, 2023 16:55
Copy link
Contributor

@sebastienverreault sebastienverreault left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spread calcs look good

@thevaibhav-dixit
Copy link
Contributor

closing since most of the work on this has been done via multiple smaller pr's.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants