Skip to content

Commit

Permalink
Introduce cashu and cdk-common crates.
Browse files Browse the repository at this point in the history
The `cashu` crates have all the types belonging to the protocol, allowing
anyone to implement a compatible Cashu server if they so wish.

The `cdk-common` has all the common types needed in the cdk and their
sub-crates
  • Loading branch information
crodas committed Jan 9, 2025
1 parent 94a4c87 commit bff8f2a
Show file tree
Hide file tree
Showing 48 changed files with 561 additions and 907 deletions.
433 changes: 180 additions & 253 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 0 additions & 13 deletions crates/cashu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ swagger = ["dep:utoipa"]
bench = []

[dependencies]
async-trait = "0.1"
bitcoin = { version = "0.32.2", features = [
"base64",
"serde",
Expand All @@ -27,18 +26,6 @@ tracing = "0.1"
url = "2.3"
uuid = { version = "1", features = ["v4", "serde"] }
utoipa = { version = "4", optional = true }
futures = "0.3.31"
anyhow = "1.0"
reqwest = { version = "0.12", default-features = false, features = [
"json",
"rustls-tls",
"rustls-tls-native-roots",
"socks",
"zstd",
"brotli",
"gzip",
"deflate",
] }
serde_json = "1"
serde_with = "3"

Expand Down
17 changes: 1 addition & 16 deletions crates/cashu/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
//! Cashu shared types and functions.
//! CDK common types and traits
//!
//! This crate is the base foundation to build things that can interact with the CDK (Cashu
//! Development Kit) and their internal crates.
//!
//! This is meant to contain the shared types, traits and common functions that are used across the
//! internal crates.
pub mod amount;
pub mod common;
pub mod database;
pub mod dhke;
pub mod error;
pub mod lightning;
pub mod mint;
pub mod mint_url;
pub mod nuts;
pub mod pub_sub;
pub mod secret;
pub mod signatory;
pub mod util;
pub mod wallet;

// re-exporting external crates
pub use lightning_invoice::{self, Bolt11Invoice};
pub use {bitcoin, reqwest};

pub use self::amount::Amount;
pub use self::nuts::*;
Expand Down
55 changes: 5 additions & 50 deletions crates/cashu/src/nuts/nut17/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//! Specific Subscription for the cdk crate
use std::str::FromStr;

use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand All @@ -9,45 +7,20 @@ use super::PublicKey;
use crate::nuts::{
CurrencyUnit, MeltQuoteBolt11Response, MintQuoteBolt11Response, PaymentMethod, ProofState,
};
use crate::pub_sub::index::{Index, Indexable, SubscriptionGlobalId};
use crate::pub_sub::SubId;

pub mod ws;

/// Subscription Parameter according to the standard
#[derive(Debug, Clone, Serialize, Eq, PartialEq, Hash, Deserialize)]
pub struct Params {
#[serde(bound = "I: DeserializeOwned + Serialize")]
pub struct Params<I> {
/// Kind
pub kind: Kind,
/// Filters
pub filters: Vec<String>,
/// Subscription Id
#[serde(rename = "subId")]
pub id: SubId,
}

impl TryFrom<Params> for Vec<Index<Notification>> {
type Error = Error;

fn try_from(val: Params) -> Result<Self, Self::Error> {
let sub_id: SubscriptionGlobalId = Default::default();
val.filters
.into_iter()
.map(|filter| {
let idx = match val.kind {
Kind::Bolt11MeltQuote => {
Notification::MeltQuoteBolt11(Uuid::from_str(&filter)?)
}
Kind::Bolt11MintQuote => {
Notification::MintQuoteBolt11(Uuid::from_str(&filter)?)
}
Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?),
};

Ok(Index::from((idx, val.id.clone(), sub_id)))
})
.collect::<Result<_, _>>()
}
pub id: I,
}

/// Check state Settings
Expand Down Expand Up @@ -106,24 +79,6 @@ pub enum NotificationPayload<T> {
MintQuoteBolt11Response(MintQuoteBolt11Response<T>),
}

impl Indexable for NotificationPayload<Uuid> {
type Type = Notification;

fn to_indexes(&self) -> Vec<Index<Self::Type>> {
match self {
NotificationPayload::ProofState(proof_state) => {
vec![Index::from(Notification::ProofState(proof_state.y))]
}
NotificationPayload::MeltQuoteBolt11Response(melt_quote) => {
vec![Index::from(Notification::MeltQuoteBolt11(melt_quote.quote))]
}
NotificationPayload::MintQuoteBolt11Response(mint_quote) => {
vec![Index::from(Notification::MintQuoteBolt11(mint_quote.quote))]
}
}
}
}

impl<T> From<ProofState> for NotificationPayload<T> {
fn from(proof_state: ProofState) -> NotificationPayload<T> {
NotificationPayload::ProofState(proof_state)
Expand Down Expand Up @@ -165,8 +120,8 @@ pub enum Kind {
ProofState,
}

impl AsRef<SubId> for Params {
fn as_ref(&self) -> &SubId {
impl<I> AsRef<I> for Params<I> {
fn as_ref(&self) -> &I {
&self.id
}
}
Expand Down
107 changes: 38 additions & 69 deletions crates/cashu/src/nuts/nut17/ws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,120 +2,107 @@
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::{NotificationPayload, Params};
use crate::pub_sub::SubId;

/// JSON RPC version
pub const JSON_RPC_VERSION: &str = "2.0";

/// The response to a subscription request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsSubscribeResponse {
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsSubscribeResponse<I> {
/// Status
pub status: String,
/// Subscription ID
#[serde(rename = "subId")]
pub sub_id: SubId,
pub sub_id: I,
}

/// The response to an unsubscription request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsUnsubscribeResponse {
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsUnsubscribeResponse<I> {
/// Status
pub status: String,
/// Subscription ID
#[serde(rename = "subId")]
pub sub_id: SubId,
pub sub_id: I,
}

/// The notification
///
/// This is the notification that is sent to the client when an event matches a
/// subscription
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "T: Serialize + DeserializeOwned")]
pub struct NotificationInner<T> {
#[serde(bound = "T: Serialize + DeserializeOwned, I: Serialize + DeserializeOwned")]
pub struct NotificationInner<T, I> {
/// The subscription ID
#[serde(rename = "subId")]
pub sub_id: SubId,
pub sub_id: I,

/// The notification payload
pub payload: NotificationPayload<T>,
}

impl From<NotificationInner<Uuid>> for NotificationInner<String> {
fn from(value: NotificationInner<Uuid>) -> Self {
NotificationInner {
sub_id: value.sub_id,
payload: match value.payload {
NotificationPayload::ProofState(pk) => NotificationPayload::ProofState(pk),
NotificationPayload::MeltQuoteBolt11Response(quote) => {
NotificationPayload::MeltQuoteBolt11Response(quote.to_string_id())
}
NotificationPayload::MintQuoteBolt11Response(quote) => {
NotificationPayload::MintQuoteBolt11Response(quote.to_string_id())
}
},
}
}
}

/// Responses from the web socket server
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
#[serde(untagged)]
pub enum WsResponseResult {
pub enum WsResponseResult<I> {
/// A response to a subscription request
Subscribe(WsSubscribeResponse),
Subscribe(WsSubscribeResponse<I>),
/// Unsubscribe
Unsubscribe(WsUnsubscribeResponse),
Unsubscribe(WsUnsubscribeResponse<I>),
}

impl From<WsSubscribeResponse> for WsResponseResult {
fn from(response: WsSubscribeResponse) -> Self {
impl<I> From<WsSubscribeResponse<I>> for WsResponseResult<I> {
fn from(response: WsSubscribeResponse<I>) -> Self {
WsResponseResult::Subscribe(response)
}
}

impl From<WsUnsubscribeResponse> for WsResponseResult {
fn from(response: WsUnsubscribeResponse) -> Self {
impl<I> From<WsUnsubscribeResponse<I>> for WsResponseResult<I> {
fn from(response: WsUnsubscribeResponse<I>) -> Self {
WsResponseResult::Unsubscribe(response)
}
}

/// The request to unsubscribe
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsUnsubscribeRequest {
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsUnsubscribeRequest<I> {
/// Subscription ID
#[serde(rename = "subId")]
pub sub_id: SubId,
pub sub_id: I,
}

/// The inner method of the websocket request
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "method", content = "params")]
pub enum WsMethodRequest {
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub enum WsMethodRequest<I> {
/// Subscribe method
Subscribe(Params),
Subscribe(Params<I>),
/// Unsubscribe method
Unsubscribe(WsUnsubscribeRequest),
Unsubscribe(WsUnsubscribeRequest<I>),
}

/// Websocket request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsRequest {
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsRequest<I> {
/// JSON RPC version
pub jsonrpc: String,
/// The method body
#[serde(flatten)]
pub method: WsMethodRequest,
pub method: WsMethodRequest<I>,
/// The request ID
pub id: usize,
}

impl From<(WsMethodRequest, usize)> for WsRequest {
fn from((method, id): (WsMethodRequest, usize)) -> Self {
impl<I> From<(WsMethodRequest<I>, usize)> for WsRequest<I> {
fn from((method, id): (WsMethodRequest<I>, usize)) -> Self {
WsRequest {
jsonrpc: JSON_RPC_VERSION.to_owned(),
method,
Expand Down Expand Up @@ -146,11 +133,12 @@ pub struct WsErrorBody {

/// Websocket response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsResponse {
#[serde(bound = "I: Serialize + DeserializeOwned")]
pub struct WsResponse<I> {
/// JSON RPC version
pub jsonrpc: String,
/// The result
pub result: WsResponseResult,
pub result: WsResponseResult<I>,
/// The request ID
pub id: usize,
}
Expand All @@ -168,18 +156,19 @@ pub struct WsErrorResponse {

/// Message from the server to the client
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "I: Serialize + DeserializeOwned")]
#[serde(untagged)]
pub enum WsMessageOrResponse {
pub enum WsMessageOrResponse<I> {
/// A response to a request
Response(WsResponse),
Response(WsResponse<I>),
/// An error response
ErrorResponse(WsErrorResponse),
/// A notification
Notification(WsNotification<NotificationInner<String>>),
Notification(WsNotification<NotificationInner<String, I>>),
}

impl From<(usize, Result<WsResponseResult, WsErrorBody>)> for WsMessageOrResponse {
fn from((id, result): (usize, Result<WsResponseResult, WsErrorBody>)) -> Self {
impl<I> From<(usize, Result<WsResponseResult<I>, WsErrorBody>)> for WsMessageOrResponse<I> {
fn from((id, result): (usize, Result<WsResponseResult<I>, WsErrorBody>)) -> Self {
match result {
Ok(result) => WsMessageOrResponse::Response(WsResponse {
jsonrpc: JSON_RPC_VERSION.to_owned(),
Expand All @@ -194,23 +183,3 @@ impl From<(usize, Result<WsResponseResult, WsErrorBody>)> for WsMessageOrRespons
}
}
}

impl From<NotificationInner<Uuid>> for WsMessageOrResponse {
fn from(notification: NotificationInner<Uuid>) -> Self {
WsMessageOrResponse::Notification(WsNotification {
jsonrpc: JSON_RPC_VERSION.to_owned(),
method: "subscribe".to_string(),
params: notification.into(),
})
}
}

impl From<NotificationInner<String>> for WsMessageOrResponse {
fn from(notification: NotificationInner<String>) -> Self {
WsMessageOrResponse::Notification(WsNotification {
jsonrpc: JSON_RPC_VERSION.to_owned(),
method: "subscribe".to_string(),
params: notification,
})
}
}
Loading

0 comments on commit bff8f2a

Please sign in to comment.