rename token

This commit is contained in:
thesimplekid
2023-05-05 20:36:21 -04:00
parent d138135613
commit 92e91b7d9b
6 changed files with 139 additions and 85 deletions

View File

@@ -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);

View File

@@ -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()
}
}

View File

@@ -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>>()?;
let keys = minreq::get(url.clone()).send()?.json::<Value>()?;
Ok(MintKeys(
keys.into_iter()
.map(|(k, v)| {
(
k,
PublicKey::from_sec1_bytes(&hex::decode(v).unwrap()).unwrap(),
)
})
.collect(),
))
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())
}

View File

@@ -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;

View File

@@ -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),
}

View File

@@ -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);
}