mirror of
https://github.com/aljazceru/cdk.git
synced 2026-01-04 13:35:20 +01:00
token from str
This commit is contained in:
24
.github/workflows/test.yml
vendored
Normal file
24
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Crate
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Toolchain
|
||||
# https://github.com/dtolnay/rust-toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run tests
|
||||
run: |
|
||||
rustup update
|
||||
cargo test
|
||||
@@ -10,6 +10,7 @@ description = "Cashu rust library"
|
||||
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.21.0"
|
||||
bitcoin = { version = "0.30.0", features=["serde"] }
|
||||
bitcoin_hashes = "0.12.0"
|
||||
hex = "0.4.3"
|
||||
@@ -18,6 +19,7 @@ minreq = { version = "2.7.0", features = ["json-using-serde", "https"] }
|
||||
rand = "0.8.5"
|
||||
secp256k1 = { version = "0.27.0", features = ["rand-std", "bitcoin-hashes-std"] }
|
||||
serde = { version = "1.0.160", features = ["derive"]}
|
||||
serde_json = "1.0.96"
|
||||
thiserror = "1.0.40"
|
||||
url = "2.3.1"
|
||||
|
||||
|
||||
@@ -121,10 +121,12 @@ impl CashuMint {
|
||||
/// Spendable check [NUT-07]
|
||||
pub async fn check_spendable(
|
||||
&self,
|
||||
proofs: Vec<Proof>,
|
||||
proofs: &Vec<Proof>,
|
||||
) -> Result<CheckSpendableResponse, Error> {
|
||||
let url = self.url.join("check")?;
|
||||
let request = CheckSpendableRequest { proofs };
|
||||
let request = CheckSpendableRequest {
|
||||
proofs: proofs.to_owned(),
|
||||
};
|
||||
|
||||
Ok(minreq::post(url)
|
||||
.with_json(&request)?
|
||||
|
||||
43
src/cashu_wallet.rs
Normal file
43
src/cashu_wallet.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use bitcoin::Amount;
|
||||
|
||||
use crate::{
|
||||
cashu_mint::CashuMint,
|
||||
error::Error,
|
||||
types::{MintKeys, Proof, ProofsStatus, RequestMintResponse},
|
||||
};
|
||||
|
||||
pub struct CashuWallet {
|
||||
pub mint: CashuMint,
|
||||
pub keys: MintKeys,
|
||||
}
|
||||
|
||||
impl CashuWallet {
|
||||
pub fn new(mint: CashuMint, keys: MintKeys) -> Self {
|
||||
Self { mint, keys }
|
||||
}
|
||||
|
||||
/// Check if a proof is spent
|
||||
pub async fn check_proofs_spent(&self, proofs: Vec<Proof>) -> Result<ProofsStatus, Error> {
|
||||
let spendable = self.mint.check_spendable(&proofs).await?;
|
||||
|
||||
let (spendable, spent): (Vec<_>, Vec<_>) = proofs
|
||||
.iter()
|
||||
.zip(spendable.spendable.iter())
|
||||
.partition(|(_, &b)| b);
|
||||
|
||||
Ok(ProofsStatus {
|
||||
spendable: spendable.into_iter().map(|(s, _)| s).cloned().collect(),
|
||||
spent: spent.into_iter().map(|(s, _)| s).cloned().collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Request Mint
|
||||
pub async fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
|
||||
self.mint.request_mint(amount).await
|
||||
}
|
||||
|
||||
/// Check fee
|
||||
pub async fn check_fee(&self, invoice: lightning_invoice::Invoice) -> Result<Amount, Error> {
|
||||
Ok(self.mint.check_fees(invoice).await?.fee)
|
||||
}
|
||||
}
|
||||
14
src/error.rs
14
src/error.rs
@@ -1,3 +1,5 @@
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Min req error
|
||||
@@ -9,4 +11,16 @@ pub enum Error {
|
||||
/// Secp245k1
|
||||
#[error("secp256k1 error: {0}")]
|
||||
Secpk256k1Error(#[from] secp256k1::Error),
|
||||
/// Unsupported Token
|
||||
#[error("Unsupported Token")]
|
||||
UnsupportedToken,
|
||||
/// Utf8 parse error
|
||||
#[error("utf8error error: {0}")]
|
||||
Utf8ParseError(#[from] FromUtf8Error),
|
||||
/// Serde Json error
|
||||
#[error("Serde Json error: {0}")]
|
||||
SerdeJsonError(#[from] serde_json::Error),
|
||||
/// Base64 error
|
||||
#[error("Base64 error: {0}")]
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod cashu_mint;
|
||||
pub mod cashu_wallet;
|
||||
pub mod dhke;
|
||||
pub mod error;
|
||||
pub mod serde_utils;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
21
src/serde_utils.rs
Normal file
21
src/serde_utils.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! Utilities for serde
|
||||
|
||||
pub mod serde_url {
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
pub fn serialize<S>(url: &Url, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(url.as_ref())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Url, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let url_string = String::deserialize(deserializer)?;
|
||||
Url::parse(&url_string).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
92
src/types.rs
92
src/types.rs
@@ -1,14 +1,16 @@
|
||||
//! Types for `cashu-rs`
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use bitcoin::Amount;
|
||||
use lightning_invoice::Invoice;
|
||||
use rand::Rng;
|
||||
use secp256k1::{PublicKey, SecretKey};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use url::Url;
|
||||
|
||||
use crate::{dhke::blind_message, error::Error, utils::split_amount};
|
||||
use crate::{dhke::blind_message, error::Error, serde_utils::serde_url, utils::split_amount};
|
||||
|
||||
/// Blinded Message [NUT-00]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -53,6 +55,28 @@ impl BlindedMessages {
|
||||
|
||||
Ok(blinded_messages)
|
||||
}
|
||||
|
||||
pub fn blank() -> Result<Self, Error> {
|
||||
let mut blinded_messages = BlindedMessages::default();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
for _i in 0..4 {
|
||||
let bytes: [u8; 32] = rng.gen();
|
||||
let (blinded, r) = blind_message(&bytes, None)?;
|
||||
|
||||
let blinded_message = BlindedMessage {
|
||||
amount: Amount::ZERO,
|
||||
b: blinded,
|
||||
};
|
||||
|
||||
blinded_messages.secrets.push(bytes.to_vec());
|
||||
blinded_messages.blinded_messages.push(blinded_message);
|
||||
blinded_messages.rs.push(r);
|
||||
blinded_messages.amounts.push(Amount::ZERO);
|
||||
}
|
||||
|
||||
Ok(blinded_messages)
|
||||
}
|
||||
}
|
||||
|
||||
/// Promise (BlindedSignature) [NIP-00]
|
||||
@@ -182,11 +206,17 @@ pub struct CheckSpendableResponse {
|
||||
pub spendable: Vec<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProofsStatus {
|
||||
pub spendable: Vec<Proof>,
|
||||
pub spent: Vec<Proof>,
|
||||
}
|
||||
|
||||
/// Mint Version
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MintVersion {
|
||||
name: String,
|
||||
version: String,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Serialize for MintVersion {
|
||||
@@ -236,3 +266,57 @@ pub struct MintInfo {
|
||||
/// message of the day that the wallet must display to the user
|
||||
pub motd: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Token {
|
||||
#[serde(with = "serde_url")]
|
||||
pub mint: Url,
|
||||
pub proofs: Vec<Proof>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TokenData {
|
||||
pub token: Vec<Token>,
|
||||
pub memo: Option<String>,
|
||||
}
|
||||
|
||||
impl FromStr for TokenData {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if !s.starts_with("cashuA") {
|
||||
return Err(Error::UnsupportedToken);
|
||||
}
|
||||
|
||||
let s = s.replace("cashuA", "");
|
||||
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)?;
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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();
|
||||
|
||||
assert_eq!(proof[0].clone().id.unwrap(), "DSAl9nvvyfva");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_from_str() {
|
||||
let token = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJpZCI6IkRTQWw5bnZ2eWZ2YSIsImFtb3VudCI6Miwic2VjcmV0IjoiRWhwZW5uQzlxQjNpRmxXOEZaX3BadyIsIkMiOiIwMmMwMjAwNjdkYjcyN2Q1ODZiYzMxODNhZWNmOTdmY2I4MDBjM2Y0Y2M0NzU5ZjY5YzYyNmM5ZGI1ZDhmNWI1ZDQifSx7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50Ijo4LCJzZWNyZXQiOiJUbVM2Q3YwWVQ1UFVfNUFUVktudWt3IiwiQyI6IjAyYWM5MTBiZWYyOGNiZTVkNzMyNTQxNWQ1YzI2MzAyNmYxNWY5Yjk2N2EwNzljYTk3NzlhYjZlNWMyZGIxMzNhNyJ9XX1dLCJtZW1vIjoiVGhhbmt5b3UuIn0=";
|
||||
let token = TokenData::from_str(token).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
token.token[0].mint,
|
||||
Url::from_str("https://8333.space:3338").unwrap()
|
||||
);
|
||||
assert_eq!(token.token[0].proofs[0].clone().id.unwrap(), "DSAl9nvvyfva");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ async fn test_request_mint() {
|
||||
assert!(mint.pr.check_signature().is_ok())
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test_mint() {
|
||||
let url = Url::from_str(MINTURL).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user