cashu add UncheckedUrl type

This is needed to track if the token mint url
has a traling "/". This is needed to round trip
serialize a token.

Though it would not actually effect being able
to redeam the token or now.
This commit is contained in:
thesimplekid
2023-09-10 11:03:55 +01:00
parent e47762029e
commit d36262233f
9 changed files with 157 additions and 19 deletions

View File

@@ -56,3 +56,11 @@ impl From<cashu::nuts::nut02::Error> for CashuError {
}
}
}
impl From<cashu::url::Error> for CashuError {
fn from(err: cashu::url::Error) -> Self {
Self::Generic {
err: err.to_string(),
}
}
}

View File

@@ -1,8 +1,10 @@
use cashu::nuts::nut00::MintProofs as MintProofsSdk;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
use cashu::nuts::nut00::MintProofs as MintProofsSdk;
use cashu::url::UncheckedUrl;
use crate::error::Result;
use crate::Proof;
@@ -19,7 +21,7 @@ impl Deref for MintProofs {
impl MintProofs {
pub fn new(mint: String, proofs: Vec<Arc<Proof>>) -> Result<Self> {
let mint = url::Url::from_str(&mint)?;
let mint = UncheckedUrl::from_str(&mint)?;
let proofs = proofs.iter().map(|p| p.as_ref().deref().clone()).collect();
Ok(Self {

View File

@@ -2,6 +2,7 @@ use std::str::FromStr;
use std::sync::Arc;
use cashu::nuts::nut00::wallet::Token as TokenSdk;
use cashu::url::UncheckedUrl;
use crate::error::Result;
use crate::MintProofs;
@@ -13,7 +14,7 @@ pub struct Token {
impl Token {
pub fn new(mint: String, proofs: Vec<Arc<Proof>>, memo: Option<String>) -> Result<Self> {
let mint = url::Url::from_str(&mint)?;
let mint = UncheckedUrl::from_str(&mint)?;
let proofs = proofs.into_iter().map(|p| p.as_ref().into()).collect();
Ok(Self {
inner: TokenSdk::new(mint, proofs, memo)?,

View File

@@ -134,7 +134,7 @@ impl Wallet {
pub fn mint_token(&self, amount: Amount, hash: &str) -> Result<Token, Error> {
let proofs = self.mint(amount, hash)?;
let token = Token::new(self.client.client.mint_url.clone(), proofs, None);
let token = Token::new(self.client.client.mint_url.clone().into(), proofs, None);
Ok(token?)
}
@@ -243,7 +243,7 @@ impl Wallet {
{
self.mint_keys.clone()
} else {
Client::new(token.mint.as_str())?.get_keys()?
Client::new(&token.mint.to_string())?.get_keys()?
};
// Sum amount of all proofs
@@ -520,7 +520,10 @@ impl Wallet {
#[cfg(feature = "blocking")]
pub fn proofs_to_token(&self, proofs: Proofs, memo: Option<String>) -> Result<String, Error> {
Ok(Token::new(self.client.client.mint_url.clone(), proofs, memo)?.convert_to_string()?)
Ok(
Token::new(self.client.client.mint_url.clone().into(), proofs, memo)?
.convert_to_string()?,
)
}
}

View File

@@ -102,6 +102,8 @@ pub mod wallet {
UnsupportedToken,
/// Token Requires proofs
ProofsRequired,
/// Url Parse error
UrlParse,
/// Custom Error message
CustomError(String),
}
@@ -118,6 +120,7 @@ pub mod wallet {
Error::UnsupportedToken => write!(f, "Unsuppported Token"),
Error::EllipticError(err) => write!(f, "{}", err),
Error::SerdeJsonError(err) => write!(f, "{}", err),
Error::UrlParse => write!(f, "Could not parse url"),
Error::ProofsRequired => write!(f, "Token must have at least one proof",),
}
}
@@ -146,6 +149,12 @@ pub mod wallet {
Error::Base64Error(err)
}
}
impl From<crate::url::Error> for Error {
fn from(_err: crate::url::Error) -> Error {
Error::UrlParse
}
}
}
#[cfg(feature = "mint")]

View File

@@ -6,6 +6,7 @@ pub mod nuts;
pub mod secret;
pub mod serde_utils;
pub mod types;
pub mod url;
pub mod utils;
pub use amount::Amount;

View File

@@ -1,10 +1,7 @@
//! Notation and Models
// https://github.com/cashubtc/nuts/blob/main/00.md
use url::Url;
use crate::Amount;
use crate::{secret::Secret, serde_utils::serde_url};
use crate::{secret::Secret, url::UncheckedUrl, Amount};
use serde::{Deserialize, Serialize};
use super::nut01::PublicKey;
@@ -34,6 +31,7 @@ pub mod wallet {
use crate::nuts::nut00::Proofs;
use crate::nuts::nut01;
use crate::secret::Secret;
use crate::url::UncheckedUrl;
use crate::Amount;
use crate::{dhke::blind_message, utils::split_amount};
@@ -111,7 +109,7 @@ pub mod wallet {
impl Token {
pub fn new(
mint_url: Url,
mint_url: UncheckedUrl,
proofs: Proofs,
memo: Option<String>,
) -> Result<Self, wallet::Error> {
@@ -119,8 +117,11 @@ pub mod wallet {
return Err(wallet::Error::ProofsRequired);
}
// Check Url is valid
let _: Url = (&mint_url).try_into()?;
Ok(Self {
token: vec![MintProofs::new(mint_url, proofs)],
token: vec![MintProofs::new(mint_url.into(), proofs)],
memo,
})
}
@@ -165,14 +166,13 @@ pub mod wallet {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintProofs {
#[serde(with = "serde_url")]
pub mint: Url,
pub mint: UncheckedUrl,
pub proofs: Proofs,
}
#[cfg(feature = "wallet")]
impl MintProofs {
fn new(mint_url: Url, proofs: Proofs) -> Self {
fn new(mint_url: UncheckedUrl, proofs: Proofs) -> Self {
Self {
mint: mint_url,
proofs,
@@ -250,7 +250,6 @@ pub mod mint {
#[cfg(test)]
mod tests {
use std::str::FromStr;
use url::Url;
use super::wallet::*;
use super::*;
@@ -268,12 +267,15 @@ mod tests {
#[test]
fn test_token_str_round_trip() {
let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJpZCI6IkRTQWw5bnZ2eWZ2YSIsImFtb3VudCI6Miwic2VjcmV0IjoiRWhwZW5uQzlxQjNpRmxXOEZaX3BadyIsIkMiOiIwMmMwMjAwNjdkYjcyN2Q1ODZiYzMxODNhZWNmOTdmY2I4MDBjM2Y0Y2M0NzU5ZjY5YzYyNmM5ZGI1ZDhmNWI1ZDQifSx7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50Ijo4LCJzZWNyZXQiOiJUbVM2Q3YwWVQ1UFVfNUFUVktudWt3IiwiQyI6IjAyYWM5MTBiZWYyOGNiZTVkNzMyNTQxNWQ1YzI2MzAyNmYxNWY5Yjk2N2EwNzljYTk3NzlhYjZlNWMyZGIxMzNhNyJ9XX1dLCJtZW1vIjoiVGhhbmt5b3UuIn0=";
let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJpZCI6IkRTQWw5bnZ2eWZ2YSIsImFtb3VudCI6Miwic2VjcmV0IjoiRWhwZW5uQzlxQjNpRmxXOEZaX3BadyIsIkMiOiIwMmMwMjAwNjdkYjcyN2Q1ODZiYzMxODNhZWNmOTdmY2I4MDBjM2Y0Y2M0NzU5ZjY5YzYyNmM5ZGI1ZDhmNWI1ZDQifSx7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50Ijo4LCJzZWNyZXQiOiJUbVM2Q3YwWVQ1UFVfNUFUVktudWt3IiwiQyI6IjAyYWM5MTBiZWYyOGNiZTVkNzMyNTQxNWQ1YzI2MzAyNmYxNWY5Yjk2N2EwNzljYTk3NzlhYjZlNWMyZGIxMzNhNyJ9XX1dLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
let token = Token::from_str(token_str).unwrap();
assert_eq!(
token.token[0].mint,
Url::from_str("https://8333.space:3338").unwrap()
UncheckedUrl::from_str("https://8333.space:3338")
.unwrap()
.into()
);
assert_eq!(
token.token[0].proofs[0].clone().id.unwrap(),

View File

@@ -78,7 +78,6 @@ impl SecretKey {
}
/// Mint Keys [NUT-01]
// TODO: CHange this to Amount type
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Keys(BTreeMap<Amount, PublicKey>);

113
crates/cashu/src/url.rs Normal file
View File

@@ -0,0 +1,113 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Distributed under the MIT software license
//! Url
use core::fmt;
use core::str::FromStr;
use serde::{Deserialize, Serialize};
use url::{ParseError, Url};
/// Url Error
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Url error
Url(ParseError),
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Url(e) => write!(f, "Url: {e}"),
}
}
}
impl From<ParseError> for Error {
fn from(e: ParseError) -> Self {
Self::Url(e)
}
}
/// Unchecked Url
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct UncheckedUrl(String);
impl UncheckedUrl {
/// New unchecked url
pub fn new<S>(url: S) -> Self
where
S: Into<String>,
{
Self(url.into())
}
/// Empty unchecked url
pub fn empty() -> Self {
Self(String::new())
}
}
impl<S> From<S> for UncheckedUrl
where
S: Into<String>,
{
fn from(url: S) -> Self {
Self(url.into())
}
}
impl FromStr for UncheckedUrl {
type Err = Error;
fn from_str(url: &str) -> Result<Self, Self::Err> {
Ok(Self::from(url))
}
}
impl TryFrom<UncheckedUrl> for Url {
type Error = Error;
fn try_from(unchecked_url: UncheckedUrl) -> Result<Url, Self::Error> {
Ok(Self::parse(&unchecked_url.0)?)
}
}
impl TryFrom<&UncheckedUrl> for Url {
type Error = Error;
fn try_from(unchecked_url: &UncheckedUrl) -> Result<Url, Self::Error> {
Ok(Self::parse(unchecked_url.0.as_str())?)
}
}
impl fmt::Display for UncheckedUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unchecked_relay_url() {
let relay = "wss://relay.damus.io/";
let relay_url = Url::from_str(relay).unwrap();
let unchecked_relay_url = UncheckedUrl::from(relay_url.clone());
assert_eq!(unchecked_relay_url, UncheckedUrl::from(relay));
assert_eq!(
Url::try_from(unchecked_relay_url.clone()).unwrap(),
relay_url
);
assert_eq!(relay, unchecked_relay_url.to_string());
}
}