소스 검색

rename token

thesimplekid 1 년 전
부모
커밋
92e91b7d9b
6개의 변경된 파일140개의 추가작업 그리고 86개의 파일을 삭제
  1. 15 19
      integration_test/src/main.rs
  2. 33 25
      src/cashu_wallet.rs
  3. 32 15
      src/client.rs
  4. 2 2
      src/dhke.rs
  5. 8 0
      src/error.rs
  6. 50 25
      src/types.rs

+ 15 - 19
integration_test/src/main.rs

@@ -7,15 +7,13 @@ use std::time::Duration;
 use bitcoin::Amount;
 use cashu_crab::cashu_wallet::CashuWallet;
 use cashu_crab::client::Client;
-use cashu_crab::types::{MintKeys, Proof, Token, TokenData};
+use cashu_crab::types::{MintKeys, MintProofs, Proofs, Token};
 use lightning_invoice::Invoice;
-use url::Url;
 
 #[tokio::main]
 async fn main() {
-    let url =
-        Url::from_str("https://legend.lnbits.com/cashu/api/v1/SKvHRus9dmjWHhstHrsazW/").unwrap();
-    let client = Client::new(url);
+    let url = "https://legend.lnbits.com/cashu/api/v1/MWMnRmbewX92AHjcL55mRo/";
+    let client = Client::new(url).unwrap();
 
     // NUT-09
     // test_get_mint_info(&mint).await;
@@ -25,16 +23,14 @@ async fn main() {
     test_get_mint_keysets(&client).await;
     test_request_mint(&wallet).await;
     let proofs = test_mint(&wallet).await;
-    let token = TokenData::new(
+    let token = Token::new(
         client.mint_url.clone(),
         proofs,
         Some("Hello World".to_string()),
     );
     let new_token = test_receive(&wallet, &token.to_string()).await;
 
-    let proofs = TokenData::from_str(&new_token).unwrap().token[0]
-        .clone()
-        .proofs;
+    let proofs = Token::from_str(&new_token).unwrap().token[0].clone().proofs;
     test_send(&wallet, proofs.clone()).await;
 
     let spendable = test_check_spendable(&client, &new_token).await;
@@ -60,13 +56,13 @@ async fn test_get_mint_keysets(client: &Client) {
 }
 
 async fn test_request_mint(wallet: &CashuWallet) {
-    let mint = wallet.request_mint(Amount::from_sat(21)).await.unwrap();
+    let mint = wallet.request_mint(Amount::from_sat(5)).await.unwrap();
 
     assert!(mint.pr.check_signature().is_ok())
 }
 
-async fn test_mint(wallet: &CashuWallet) -> Vec<Proof> {
-    let mint_req = wallet.request_mint(Amount::from_sat(21)).await.unwrap();
+async fn test_mint(wallet: &CashuWallet) -> Proofs {
+    let mint_req = wallet.request_mint(Amount::from_sat(5)).await.unwrap();
     println!("Mint Req: {:?}", mint_req.pr.to_string());
 
     // Since before the mint happens the invoice in the mint req has to be paid this wait is here
@@ -75,7 +71,7 @@ async fn test_mint(wallet: &CashuWallet) -> Vec<Proof> {
     thread::sleep(Duration::from_secs(30));
 
     wallet
-        .mint_token(Amount::from_sat(21), &mint_req.hash)
+        .mint_token(Amount::from_sat(5), &mint_req.hash)
         .await
         .unwrap()
 
@@ -92,12 +88,12 @@ async fn test_check_fees(mint: &Client) {
 async fn test_receive(wallet: &CashuWallet, token: &str) -> String {
     let prom = wallet.receive(token).await.unwrap();
     println!("{:?}", prom);
-    let token = Token {
+    let token = MintProofs {
         mint: wallet.client.mint_url.clone(),
         proofs: prom,
     };
 
-    let token = TokenData {
+    let token = Token {
         token: vec![token],
         memo: Some("Hello world".to_string()),
     };
@@ -107,12 +103,12 @@ async fn test_receive(wallet: &CashuWallet, token: &str) -> String {
     s
 }
 
-async fn test_check_spendable(client: &Client, token: &str) -> Vec<Proof> {
+async fn test_check_spendable(client: &Client, token: &str) -> Proofs {
     let mint_keys = client.get_keys().await.unwrap();
 
     let wallet = CashuWallet::new(client.to_owned(), mint_keys);
 
-    let token_data = TokenData::from_str(token).unwrap();
+    let token_data = Token::from_str(token).unwrap();
     let spendable = wallet
         .check_proofs_spent(token_data.token[0].clone().proofs)
         .await
@@ -124,7 +120,7 @@ async fn test_check_spendable(client: &Client, token: &str) -> Vec<Proof> {
     spendable.spendable
 }
 
-async fn test_send(wallet: &CashuWallet, proofs: Vec<Proof>) {
+async fn test_send(wallet: &CashuWallet, proofs: Proofs) {
     let send = wallet.send(Amount::from_sat(2), proofs).await.unwrap();
 
     println!("{:?}", send);
@@ -137,7 +133,7 @@ async fn test_send(wallet: &CashuWallet, proofs: Vec<Proof>) {
     println!("Send Token: {send_token}");
 }
 
-async fn test_melt(wallet: &CashuWallet, invoice: Invoice, proofs: Vec<Proof>) {
+async fn test_melt(wallet: &CashuWallet, invoice: Invoice, proofs: Proofs) {
     let res = wallet.melt(invoice, proofs).await.unwrap();
 
     println!("{:?}", res);

+ 33 - 25
src/cashu_wallet.rs

@@ -8,23 +8,31 @@ use crate::{
     dhke::construct_proofs,
     error::Error,
     types::{
-        BlindedMessages, Melted, MintKeys, Proof, ProofsStatus, RequestMintResponse, SendProofs,
-        SplitPayload, SplitRequest, TokenData,
+        BlindedMessages, Melted, MintKeys, Proofs, ProofsStatus, RequestMintResponse, SendProofs,
+        SplitPayload, SplitRequest, Token,
     },
 };
 
+#[derive(Clone, Debug)]
 pub struct CashuWallet {
     pub client: Client,
     pub mint_keys: MintKeys,
+    pub balance: Amount,
 }
 
 impl CashuWallet {
     pub fn new(client: Client, mint_keys: MintKeys) -> Self {
-        Self { client, mint_keys }
+        Self {
+            client,
+            mint_keys,
+            balance: Amount::ZERO,
+        }
     }
 
+    // TODO: getter method for keys that if it cant get them try agian
+
     /// Check if a proof is spent
-    pub async fn check_proofs_spent(&self, proofs: Vec<Proof>) -> Result<ProofsStatus, Error> {
+    pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<ProofsStatus, Error> {
         let spendable = self.client.check_spendable(&proofs).await?;
 
         // Separate proofs in spent and unspent based on mint response
@@ -45,7 +53,7 @@ impl CashuWallet {
     }
 
     /// Mint Token
-    pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result<Vec<Proof>, Error> {
+    pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result<Proofs, Error> {
         let blinded_messages = BlindedMessages::random(amount)?;
 
         let mint_res = self.client.mint(blinded_messages.clone(), hash).await?;
@@ -66,8 +74,8 @@ impl CashuWallet {
     }
 
     /// Receive
-    pub async fn receive(&self, encoded_token: &str) -> Result<Vec<Proof>, Error> {
-        let token_data = TokenData::from_str(encoded_token)?;
+    pub async fn receive(&self, encoded_token: &str) -> Result<Proofs, Error> {
+        let token_data = Token::from_str(encoded_token)?;
 
         let mut proofs = vec![];
         for token in token_data.token {
@@ -75,13 +83,12 @@ impl CashuWallet {
                 continue;
             }
 
-            let keys = if token.mint.eq(&self.client.mint_url) {
+            let keys = if token.mint.to_string().eq(&self.client.mint_url.to_string()) {
                 self.mint_keys.clone()
             } else {
-                // FIXME:
-                println!("No match");
-                self.mint_keys.clone()
-                // CashuMint::new(token.mint).get_keys().await.unwrap()
+                // println!("dd");
+                // self.mint_keys.clone()
+                Client::new(token.mint.as_str())?.get_keys().await.unwrap()
             };
 
             // Sum amount of all proofs
@@ -90,11 +97,13 @@ impl CashuWallet {
                 .iter()
                 .fold(Amount::ZERO, |acc, p| acc + p.amount);
 
-            let split_payload = self
-                .create_split(Amount::ZERO, amount, token.proofs)
-                .await?;
+            let split_payload = self.create_split(Amount::ZERO, amount, token.proofs)?;
 
-            let split_response = self.client.split(split_payload.split_payload).await?;
+            let split_response = self
+                .client
+                .split(split_payload.split_payload)
+                .await
+                .unwrap();
 
             // Proof to keep
             let keep_proofs = construct_proofs(
@@ -120,11 +129,11 @@ impl CashuWallet {
     }
 
     /// Create Split Payload
-    async fn create_split(
+    fn create_split(
         &self,
         keep_amount: Amount,
         send_amount: Amount,
-        proofs: Vec<Proof>,
+        proofs: Proofs,
     ) -> Result<SplitPayload, Error> {
         let keep_blinded_messages = BlindedMessages::random(keep_amount)?;
         let send_blinded_messages = BlindedMessages::random(send_amount)?;
@@ -148,7 +157,7 @@ impl CashuWallet {
     }
 
     /// Send
-    pub async fn send(&self, amount: Amount, proofs: Vec<Proof>) -> Result<SendProofs, Error> {
+    pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result<SendProofs, Error> {
         let mut amount_available = Amount::ZERO;
         let mut send_proofs = SendProofs::default();
 
@@ -175,9 +184,8 @@ impl CashuWallet {
         let amount_to_keep = amount_available - amount;
         let amount_to_send = amount;
 
-        let split_payload = self
-            .create_split(amount_to_keep, amount_to_send, send_proofs.send_proofs)
-            .await?;
+        let split_payload =
+            self.create_split(amount_to_keep, amount_to_send, send_proofs.send_proofs)?;
 
         let split_response = self.client.split(split_payload.split_payload).await?;
 
@@ -209,7 +217,7 @@ impl CashuWallet {
     pub async fn melt(
         &self,
         invoice: lightning_invoice::Invoice,
-        proofs: Vec<Proof>,
+        proofs: Proofs,
     ) -> Result<Melted, Error> {
         let change = BlindedMessages::blank()?;
         let melt_response = self
@@ -234,7 +242,7 @@ impl CashuWallet {
         })
     }
 
-    pub fn proofs_to_token(&self, proofs: Vec<Proof>, memo: Option<String>) -> String {
-        TokenData::new(self.client.mint_url.clone(), proofs, memo).to_string()
+    pub fn proofs_to_token(&self, proofs: Proofs, memo: Option<String>) -> String {
+        Token::new(self.client.mint_url.clone(), proofs, memo).to_string()
     }
 }

+ 32 - 15
src/client.rs

@@ -24,25 +24,42 @@ pub struct Client {
 }
 
 impl Client {
-    pub fn new(mint_url: Url) -> Self {
-        Self { mint_url }
+    pub fn new(mint_url: &str) -> Result<Self, Error> {
+        // HACK
+        let mut mint_url = String::from(mint_url);
+        if !mint_url.ends_with('/') {
+            mint_url.push('/');
+        }
+        let mint_url = Url::parse(&mint_url).unwrap();
+        Ok(Self { mint_url })
     }
 
     /// Get Mint Keys [NUT-01]
     pub async fn get_keys(&self) -> Result<MintKeys, Error> {
         let url = self.mint_url.join("keys")?;
-        let keys = minreq::get(url).send()?.json::<HashMap<u64, String>>()?;
-
-        Ok(MintKeys(
-            keys.into_iter()
-                .map(|(k, v)| {
-                    (
-                        k,
-                        PublicKey::from_sec1_bytes(&hex::decode(v).unwrap()).unwrap(),
-                    )
-                })
-                .collect(),
-        ))
+        let keys = minreq::get(url.clone()).send()?.json::<Value>()?;
+
+        let keys: HashMap<u64, String> = match serde_json::from_value(keys.clone()) {
+            Ok(keys) => keys,
+            Err(_err) => {
+                return Err(Error::CustomError(format!(
+                    "url: {}, {}",
+                    url,
+                    serde_json::to_string(&keys)?
+                )))
+            }
+        };
+
+        let mint_keys: HashMap<u64, PublicKey> = keys
+            .into_iter()
+            .filter_map(|(k, v)| {
+                let key = hex::decode(v).ok()?;
+                let public_key = PublicKey::from_sec1_bytes(&key).ok()?;
+                Some((k, public_key))
+            })
+            .collect();
+
+        Ok(MintKeys(mint_keys))
     }
 
     /// Get Keysets [NUT-02]
@@ -126,7 +143,7 @@ impl Client {
 
         // TODO: need to handle response error
         // specifically token already spent
-        // println!("{:?}", res);
+        println!("Split Res: {:?}", res);
 
         Ok(serde_json::from_value(res).unwrap())
     }

+ 2 - 2
src/dhke.rs

@@ -7,7 +7,7 @@ use bitcoin_hashes::Hash;
 use k256::{ProjectivePoint, PublicKey, Scalar, SecretKey};
 
 use crate::error::Error;
-use crate::types::{MintKeys, Promise, Proof};
+use crate::types::{MintKeys, Promise, Proof, Proofs};
 
 fn hash_to_curve(message: &[u8]) -> PublicKey {
     let mut msg_to_hash = message.to_vec();
@@ -68,7 +68,7 @@ pub fn construct_proofs(
     rs: Vec<SecretKey>,
     secrets: Vec<String>,
     keys: &MintKeys,
-) -> Result<Vec<Proof>, Error> {
+) -> Result<Proofs, Error> {
     let mut proofs = vec![];
     for (i, promise) in promises.into_iter().enumerate() {
         let blinded_c = promise.c;

+ 8 - 0
src/error.rs

@@ -23,4 +23,12 @@ pub enum Error {
     /// Insufficaint Funds
     #[error("Not enough funds")]
     InsufficantFunds,
+    #[error("Custom error: {0}")]
+    CustomError(String),
+    /// From hex error
+    #[error("From Hex Error: {0}")]
+    HexError(#[from] hex::FromHexError),
+    /// From elliptic curve
+    #[error("From Elliptic: {0}")]
+    EllipticError(#[from] k256::elliptic_curve::Error),
 }

+ 50 - 25
src/types.rs

@@ -40,7 +40,7 @@ pub struct BlindedMessages {
 }
 
 impl BlindedMessages {
-    ///
+    /// Outputs for specfied amount with random secret
     pub fn random(amount: Amount) -> Result<Self, Error> {
         let mut blinded_messages = BlindedMessages::default();
 
@@ -59,6 +59,7 @@ impl BlindedMessages {
         Ok(blinded_messages)
     }
 
+    /// Blank Outputs used for NUT-08 change
     pub fn blank() -> Result<Self, Error> {
         let mut blinded_messages = BlindedMessages::default();
 
@@ -120,10 +121,22 @@ pub struct Proof {
     pub script: Option<String>,
 }
 
+/// List of proofs
+pub type Proofs = Vec<Proof>;
+
 /// Mint Keys [NUT-01]
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct MintKeys(pub HashMap<u64, PublicKey>);
 
+impl MintKeys {
+    pub fn as_hashmap(&self) -> HashMap<u64, String> {
+        self.0
+            .iter()
+            .map(|(k, v)| (k.to_owned(), hex::encode(v.to_sec1_bytes())))
+            .collect()
+    }
+}
+
 /// Mint Keysets [UT-02]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MintKeySets {
@@ -136,7 +149,7 @@ pub struct MintKeySets {
 pub struct RequestMintResponse {
     /// Bolt11 payment request
     pub pr: Invoice,
-    /// Random Hash
+    /// Random hash MUST not be the hash of invoice
     pub hash: String,
 }
 
@@ -171,7 +184,7 @@ pub struct CheckFeesRequest {
 /// Melt Request [NUT-05]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MeltRequest {
-    pub proofs: Vec<Proof>,
+    pub proofs: Proofs,
     /// bollt11
     pub pr: Invoice,
     /// Blinded Message that can be used to return change [NUT-08]
@@ -192,7 +205,7 @@ pub struct MeltResponse {
 pub struct Melted {
     pub paid: bool,
     pub preimage: Option<String>,
-    pub change: Option<Vec<Proof>>,
+    pub change: Option<Proofs>,
 }
 
 /// Split Request [NUT-06]
@@ -200,7 +213,7 @@ pub struct Melted {
 pub struct SplitRequest {
     #[serde(with = "bitcoin::amount::serde::as_sat")]
     pub amount: Amount,
-    pub proofs: Vec<Proof>,
+    pub proofs: Proofs,
     pub outputs: Vec<BlindedMessage>,
 }
 
@@ -216,7 +229,7 @@ pub struct SplitResponse {
 /// Check spendabale request [NUT-07]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct CheckSpendableRequest {
-    pub proofs: Vec<Proof>,
+    pub proofs: Proofs,
 }
 
 /// Check Spendable Response [NUT-07]
@@ -229,14 +242,14 @@ pub struct CheckSpendableResponse {
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct ProofsStatus {
-    pub spendable: Vec<Proof>,
-    pub spent: Vec<Proof>,
+    pub spendable: Proofs,
+    pub spent: Proofs,
 }
 
 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
 pub struct SendProofs {
-    pub change_proofs: Vec<Proof>,
-    pub send_proofs: Vec<Proof>,
+    pub change_proofs: Proofs,
+    pub send_proofs: Proofs,
 }
 
 /// Mint Version
@@ -296,14 +309,14 @@ pub struct MintInfo {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-pub struct Token {
+pub struct MintProofs {
     #[serde(with = "serde_url")]
     pub mint: Url,
-    pub proofs: Vec<Proof>,
+    pub proofs: Proofs,
 }
 
-impl Token {
-    fn new(mint_url: Url, proofs: Vec<Proof>) -> Self {
+impl MintProofs {
+    fn new(mint_url: Url, proofs: Proofs) -> Self {
         Self {
             mint: mint_url,
             proofs,
@@ -312,21 +325,33 @@ impl Token {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-pub struct TokenData {
-    pub token: Vec<Token>,
+pub struct Token {
+    pub token: Vec<MintProofs>,
     pub memo: Option<String>,
 }
 
-impl TokenData {
-    pub fn new(mint_url: Url, proofs: Vec<Proof>, memo: Option<String>) -> Self {
+impl Token {
+    pub fn new(mint_url: Url, proofs: Proofs, memo: Option<String>) -> Self {
         Self {
-            token: vec![Token::new(mint_url, proofs)],
+            token: vec![MintProofs::new(mint_url, proofs)],
             memo,
         }
     }
+
+    pub fn token_info(&self) -> (u64, String) {
+        let mut amount = Amount::ZERO;
+
+        for proofs in &self.token {
+            for proof in &proofs.proofs {
+                amount += proof.amount;
+            }
+        }
+
+        (amount.to_sat(), self.token[0].mint.to_string())
+    }
 }
 
-impl FromStr for TokenData {
+impl FromStr for Token {
     type Err = Error;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -338,12 +363,12 @@ impl FromStr for TokenData {
         let decoded = general_purpose::STANDARD.decode(s)?;
         let decoded_str = String::from_utf8(decoded)?;
         println!("decode: {:?}", decoded_str);
-        let token: TokenData = serde_json::from_str(&decoded_str)?;
+        let token: Token = serde_json::from_str(&decoded_str)?;
         Ok(token)
     }
 }
 
-impl ToString for TokenData {
+impl ToString for Token {
     fn to_string(&self) -> String {
         let json_string = serde_json::to_string(self).unwrap();
         let encoded = general_purpose::STANDARD.encode(json_string);
@@ -358,7 +383,7 @@ mod tests {
     #[test]
     fn test_proof_seralize() {
         let proof = "[{\"id\":\"DSAl9nvvyfva\",\"amount\":2,\"secret\":\"EhpennC9qB3iFlW8FZ_pZw\",\"C\":\"02c020067db727d586bc3183aecf97fcb800c3f4cc4759f69c626c9db5d8f5b5d4\"},{\"id\":\"DSAl9nvvyfva\",\"amount\":8,\"secret\":\"TmS6Cv0YT5PU_5ATVKnukw\",\"C\":\"02ac910bef28cbe5d7325415d5c263026f15f9b967a079ca9779ab6e5c2db133a7\"}]";
-        let proof: Vec<Proof> = serde_json::from_str(proof).unwrap();
+        let proof: Proofs = serde_json::from_str(proof).unwrap();
 
         assert_eq!(proof[0].clone().id.unwrap(), "DSAl9nvvyfva");
     }
@@ -366,7 +391,7 @@ mod tests {
     #[test]
     fn test_token_str_round_trip() {
         let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJpZCI6IkRTQWw5bnZ2eWZ2YSIsImFtb3VudCI6Miwic2VjcmV0IjoiRWhwZW5uQzlxQjNpRmxXOEZaX3BadyIsIkMiOiIwMmMwMjAwNjdkYjcyN2Q1ODZiYzMxODNhZWNmOTdmY2I4MDBjM2Y0Y2M0NzU5ZjY5YzYyNmM5ZGI1ZDhmNWI1ZDQifSx7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50Ijo4LCJzZWNyZXQiOiJUbVM2Q3YwWVQ1UFVfNUFUVktudWt3IiwiQyI6IjAyYWM5MTBiZWYyOGNiZTVkNzMyNTQxNWQ1YzI2MzAyNmYxNWY5Yjk2N2EwNzljYTk3NzlhYjZlNWMyZGIxMzNhNyJ9XX1dLCJtZW1vIjoiVGhhbmt5b3UuIn0=";
-        let token = TokenData::from_str(token_str).unwrap();
+        let token = Token::from_str(token_str).unwrap();
 
         assert_eq!(
             token.token[0].mint,
@@ -376,7 +401,7 @@ mod tests {
 
         let encoded = &token.to_string();
 
-        let token_data = TokenData::from_str(encoded).unwrap();
+        let token_data = Token::from_str(encoded).unwrap();
 
         assert_eq!(token_data, token);
     }