mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 14:14:49 +01:00
feat(wallet): make wallet single mint and unit
feat(wallet): cli use mint with one url and unit feat(wallet): remove p2pk keys from wallet feat(wallet): multimint wallet
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -30,7 +30,6 @@ jobs:
|
|||||||
-p cdk --no-default-features,
|
-p cdk --no-default-features,
|
||||||
-p cdk --no-default-features --features wallet,
|
-p cdk --no-default-features --features wallet,
|
||||||
-p cdk --no-default-features --features mint,
|
-p cdk --no-default-features --features mint,
|
||||||
-p cdk --no-default-features --features wallet --features nostr,
|
|
||||||
-p cdk-redb,
|
-p cdk-redb,
|
||||||
-p cdk-sqlite,
|
-p cdk-sqlite,
|
||||||
--bin cdk-cli,
|
--bin cdk-cli,
|
||||||
@@ -68,7 +67,6 @@ jobs:
|
|||||||
-p cdk,
|
-p cdk,
|
||||||
-p cdk --no-default-features,
|
-p cdk --no-default-features,
|
||||||
-p cdk --no-default-features --features wallet,
|
-p cdk --no-default-features --features wallet,
|
||||||
-p cdk --no-default-features --features wallet --features nostr,
|
|
||||||
-p cdk-js
|
-p cdk-js
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[language-server.rust-analyzer.config]
|
[language-server.rust-analyzer.config]
|
||||||
cargo = { features = ["wallet", "mint", "nostr"] }
|
cargo = { features = ["wallet", "mint"] }
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
//! Wallet Js Bindings
|
//! Wallet Js Bindings
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cdk::amount::SplitTarget;
|
use cdk::amount::SplitTarget;
|
||||||
use cdk::nuts::Proofs;
|
use cdk::nuts::{Proofs, SecretKey};
|
||||||
use cdk::url::UncheckedUrl;
|
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use cdk::Amount;
|
use cdk::Amount;
|
||||||
use cdk_rexie::RexieWalletDatabase;
|
use cdk_rexie::RexieWalletDatabase;
|
||||||
@@ -43,38 +41,10 @@ impl From<Wallet> for JsWallet {
|
|||||||
#[wasm_bindgen(js_class = Wallet)]
|
#[wasm_bindgen(js_class = Wallet)]
|
||||||
impl JsWallet {
|
impl JsWallet {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub async fn new(seed: Vec<u8>, p2pk_signing_keys: Vec<JsSecretKey>) -> Self {
|
pub async fn new(mints_url: String, unit: JsCurrencyUnit, seed: Vec<u8>) -> Self {
|
||||||
let db = RexieWalletDatabase::new().await.unwrap();
|
let db = RexieWalletDatabase::new().await.unwrap();
|
||||||
|
|
||||||
Wallet::new(
|
Wallet::new(&mints_url, unit.into(), Arc::new(db), &seed).into()
|
||||||
Arc::new(db),
|
|
||||||
&seed,
|
|
||||||
p2pk_signing_keys
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.deref().clone())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = unitBalance)]
|
|
||||||
pub async fn unit_balance(&self, unit: JsCurrencyUnit) -> Result<JsAmount> {
|
|
||||||
Ok(self
|
|
||||||
.inner
|
|
||||||
.unit_balance(unit.into())
|
|
||||||
.await
|
|
||||||
.map_err(into_err)?
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = pendingUnitBalance)]
|
|
||||||
pub async fn pending_unit_balance(&self, unit: JsCurrencyUnit) -> Result<JsAmount> {
|
|
||||||
Ok(self
|
|
||||||
.inner
|
|
||||||
.pending_unit_balance(unit.into())
|
|
||||||
.await
|
|
||||||
.map_err(into_err)?
|
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = totalBalance)]
|
#[wasm_bindgen(js_name = totalBalance)]
|
||||||
@@ -92,64 +62,36 @@ impl JsWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = checkAllPendingProofs)]
|
#[wasm_bindgen(js_name = checkAllPendingProofs)]
|
||||||
pub async fn check_all_pending_proofs(
|
pub async fn check_all_pending_proofs(&self) -> Result<JsAmount> {
|
||||||
&self,
|
|
||||||
mint_url: Option<String>,
|
|
||||||
unit: Option<JsCurrencyUnit>,
|
|
||||||
) -> Result<JsAmount> {
|
|
||||||
let mint_url = match mint_url {
|
|
||||||
Some(url) => Some(UncheckedUrl::from_str(&url).map_err(into_err)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.inner
|
.inner
|
||||||
.check_all_pending_proofs(mint_url, unit.map(|u| u.into()))
|
.check_all_pending_proofs()
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?
|
.map_err(into_err)?
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = mintBalances)]
|
#[wasm_bindgen(js_name = getMintInfo)]
|
||||||
pub async fn mint_balances(&self) -> Result<JsValue> {
|
pub async fn get_mint_info(&self) -> Result<Option<JsMintInfo>> {
|
||||||
let mint_balances = self.inner.mint_balances().await.map_err(into_err)?;
|
|
||||||
|
|
||||||
Ok(serde_wasm_bindgen::to_value(&mint_balances)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = addMint)]
|
|
||||||
pub async fn add_mint(&self, mint_url: String) -> Result<Option<JsMintInfo>> {
|
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.inner
|
.inner
|
||||||
.add_mint(mint_url)
|
.get_mint_info()
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?
|
.map_err(into_err)?
|
||||||
.map(|i| i.into()))
|
.map(|i| i.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = refreshMint)]
|
#[wasm_bindgen(js_name = refreshMint)]
|
||||||
pub async fn refresh_mint_keys(&self, mint_url: String) -> Result<()> {
|
pub async fn refresh_mint_keys(&self) -> Result<()> {
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
self.inner.refresh_mint_keys().await.map_err(into_err)?;
|
||||||
self.inner
|
|
||||||
.refresh_mint_keys(&mint_url)
|
|
||||||
.await
|
|
||||||
.map_err(into_err)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = mintQuote)]
|
#[wasm_bindgen(js_name = mintQuote)]
|
||||||
pub async fn mint_quote(
|
pub async fn mint_quote(&mut self, amount: u64) -> Result<JsMintQuote> {
|
||||||
&mut self,
|
|
||||||
mint_url: String,
|
|
||||||
amount: u64,
|
|
||||||
unit: JsCurrencyUnit,
|
|
||||||
) -> Result<JsMintQuote> {
|
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
let quote = self
|
let quote = self
|
||||||
.inner
|
.inner
|
||||||
.mint_quote(mint_url, unit.into(), amount.into())
|
.mint_quote(amount.into())
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?;
|
.map_err(into_err)?;
|
||||||
|
|
||||||
@@ -157,16 +99,10 @@ impl JsWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = mintQuoteStatus)]
|
#[wasm_bindgen(js_name = mintQuoteStatus)]
|
||||||
pub async fn mint_quote_status(
|
pub async fn mint_quote_status(&self, quote_id: String) -> Result<JsMintQuoteBolt11Response> {
|
||||||
&self,
|
|
||||||
mint_url: String,
|
|
||||||
quote_id: String,
|
|
||||||
) -> Result<JsMintQuoteBolt11Response> {
|
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
|
|
||||||
let quote = self
|
let quote = self
|
||||||
.inner
|
.inner
|
||||||
.mint_quote_status(mint_url, "e_id)
|
.mint_quote_status("e_id)
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?;
|
.map_err(into_err)?;
|
||||||
|
|
||||||
@@ -183,7 +119,6 @@ impl JsWallet {
|
|||||||
#[wasm_bindgen(js_name = mint)]
|
#[wasm_bindgen(js_name = mint)]
|
||||||
pub async fn mint(
|
pub async fn mint(
|
||||||
&mut self,
|
&mut self,
|
||||||
mint_url: String,
|
|
||||||
quote_id: String,
|
quote_id: String,
|
||||||
p2pk_condition: Option<JsP2PKSpendingConditions>,
|
p2pk_condition: Option<JsP2PKSpendingConditions>,
|
||||||
htlc_condition: Option<JsHTLCSpendingConditions>,
|
htlc_condition: Option<JsHTLCSpendingConditions>,
|
||||||
@@ -192,7 +127,6 @@ impl JsWallet {
|
|||||||
let target = split_target_amount
|
let target = split_target_amount
|
||||||
.map(|a| SplitTarget::Value(*a.deref()))
|
.map(|a| SplitTarget::Value(*a.deref()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
let conditions = match (p2pk_condition, htlc_condition) {
|
let conditions = match (p2pk_condition, htlc_condition) {
|
||||||
(Some(_), Some(_)) => {
|
(Some(_), Some(_)) => {
|
||||||
return Err(JsValue::from_str(
|
return Err(JsValue::from_str(
|
||||||
@@ -206,7 +140,7 @@ impl JsWallet {
|
|||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.inner
|
.inner
|
||||||
.mint(mint_url, "e_id, target, conditions)
|
.mint("e_id, target, conditions)
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?
|
.map_err(into_err)?
|
||||||
.into())
|
.into())
|
||||||
@@ -215,20 +149,12 @@ impl JsWallet {
|
|||||||
#[wasm_bindgen(js_name = meltQuote)]
|
#[wasm_bindgen(js_name = meltQuote)]
|
||||||
pub async fn melt_quote(
|
pub async fn melt_quote(
|
||||||
&mut self,
|
&mut self,
|
||||||
mint_url: String,
|
|
||||||
unit: JsCurrencyUnit,
|
|
||||||
request: String,
|
request: String,
|
||||||
mpp_amount: Option<JsAmount>,
|
mpp_amount: Option<JsAmount>,
|
||||||
) -> Result<JsMeltQuote> {
|
) -> Result<JsMeltQuote> {
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
let melt_quote = self
|
let melt_quote = self
|
||||||
.inner
|
.inner
|
||||||
.melt_quote(
|
.melt_quote(request, mpp_amount.map(|a| *a.deref()))
|
||||||
mint_url,
|
|
||||||
unit.into(),
|
|
||||||
request,
|
|
||||||
mpp_amount.map(|a| *a.deref()),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?;
|
.map_err(into_err)?;
|
||||||
|
|
||||||
@@ -236,16 +162,10 @@ impl JsWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = meltQuoteStatus)]
|
#[wasm_bindgen(js_name = meltQuoteStatus)]
|
||||||
pub async fn melt_quote_status(
|
pub async fn melt_quote_status(&self, quote_id: String) -> Result<JsMeltQuoteBolt11Response> {
|
||||||
&self,
|
|
||||||
mint_url: String,
|
|
||||||
quote_id: String,
|
|
||||||
) -> Result<JsMeltQuoteBolt11Response> {
|
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
|
|
||||||
let quote = self
|
let quote = self
|
||||||
.inner
|
.inner
|
||||||
.melt_quote_status(mint_url, "e_id)
|
.melt_quote_status("e_id)
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?;
|
.map_err(into_err)?;
|
||||||
|
|
||||||
@@ -255,31 +175,35 @@ impl JsWallet {
|
|||||||
#[wasm_bindgen(js_name = melt)]
|
#[wasm_bindgen(js_name = melt)]
|
||||||
pub async fn melt(
|
pub async fn melt(
|
||||||
&mut self,
|
&mut self,
|
||||||
mint_url: String,
|
|
||||||
quote_id: String,
|
quote_id: String,
|
||||||
split_target_amount: Option<JsAmount>,
|
split_target_amount: Option<JsAmount>,
|
||||||
) -> Result<JsMelted> {
|
) -> Result<JsMelted> {
|
||||||
let target = split_target_amount
|
let target = split_target_amount
|
||||||
.map(|a| SplitTarget::Value(*a.deref()))
|
.map(|a| SplitTarget::Value(*a.deref()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
|
|
||||||
let melted = self
|
let melted = self.inner.melt("e_id, target).await.map_err(into_err)?;
|
||||||
.inner
|
|
||||||
.melt(&mint_url, "e_id, target)
|
|
||||||
.await
|
|
||||||
.map_err(into_err)?;
|
|
||||||
|
|
||||||
Ok(melted.into())
|
Ok(melted.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = receive)]
|
#[wasm_bindgen(js_name = receive)]
|
||||||
pub async fn receive(&mut self, encoded_token: String, preimages: JsValue) -> Result<JsAmount> {
|
pub async fn receive(
|
||||||
let preimages: Option<Vec<String>> = serde_wasm_bindgen::from_value(preimages)?;
|
&mut self,
|
||||||
|
encoded_token: String,
|
||||||
|
signing_keys: Vec<JsSecretKey>,
|
||||||
|
preimages: Vec<String>,
|
||||||
|
) -> Result<JsAmount> {
|
||||||
|
let signing_keys: Vec<SecretKey> = signing_keys.iter().map(|s| s.deref().clone()).collect();
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.inner
|
.inner
|
||||||
.receive(&encoded_token, &SplitTarget::default(), preimages)
|
.receive(
|
||||||
|
&encoded_token,
|
||||||
|
&SplitTarget::default(),
|
||||||
|
&signing_keys,
|
||||||
|
&preimages,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?
|
.map_err(into_err)?
|
||||||
.into())
|
.into())
|
||||||
@@ -289,8 +213,6 @@ impl JsWallet {
|
|||||||
#[wasm_bindgen(js_name = send)]
|
#[wasm_bindgen(js_name = send)]
|
||||||
pub async fn send(
|
pub async fn send(
|
||||||
&mut self,
|
&mut self,
|
||||||
mint_url: String,
|
|
||||||
unit: JsCurrencyUnit,
|
|
||||||
memo: Option<String>,
|
memo: Option<String>,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
p2pk_condition: Option<JsP2PKSpendingConditions>,
|
p2pk_condition: Option<JsP2PKSpendingConditions>,
|
||||||
@@ -308,20 +230,11 @@ impl JsWallet {
|
|||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
|
|
||||||
let target = split_target_amount
|
let target = split_target_amount
|
||||||
.map(|a| SplitTarget::Value(*a.deref()))
|
.map(|a| SplitTarget::Value(*a.deref()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.inner
|
self.inner
|
||||||
.send(
|
.send(Amount::from(amount), memo, conditions, &target)
|
||||||
&mint_url,
|
|
||||||
unit.into(),
|
|
||||||
Amount::from(amount),
|
|
||||||
memo,
|
|
||||||
conditions,
|
|
||||||
&target,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)
|
.map_err(into_err)
|
||||||
}
|
}
|
||||||
@@ -330,8 +243,6 @@ impl JsWallet {
|
|||||||
#[wasm_bindgen(js_name = swap)]
|
#[wasm_bindgen(js_name = swap)]
|
||||||
pub async fn swap(
|
pub async fn swap(
|
||||||
&mut self,
|
&mut self,
|
||||||
mint_url: String,
|
|
||||||
unit: JsCurrencyUnit,
|
|
||||||
amount: u64,
|
amount: u64,
|
||||||
input_proofs: Vec<JsProof>,
|
input_proofs: Vec<JsProof>,
|
||||||
p2pk_condition: Option<JsP2PKSpendingConditions>,
|
p2pk_condition: Option<JsP2PKSpendingConditions>,
|
||||||
@@ -349,8 +260,6 @@ impl JsWallet {
|
|||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
|
|
||||||
|
|
||||||
let proofs: Proofs = input_proofs.iter().map(|p| p.deref()).cloned().collect();
|
let proofs: Proofs = input_proofs.iter().map(|p| p.deref()).cloned().collect();
|
||||||
|
|
||||||
let target = split_target_amount
|
let target = split_target_amount
|
||||||
@@ -358,14 +267,7 @@ impl JsWallet {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let post_swap_proofs = self
|
let post_swap_proofs = self
|
||||||
.inner
|
.inner
|
||||||
.swap(
|
.swap(Some(Amount::from(amount)), &target, proofs, conditions)
|
||||||
&mint_url,
|
|
||||||
&unit.into(),
|
|
||||||
Some(Amount::from(amount)),
|
|
||||||
&target,
|
|
||||||
proofs,
|
|
||||||
conditions,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(into_err)?;
|
.map_err(into_err)?;
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ license.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
bip39.workspace = true
|
bip39.workspace = true
|
||||||
cdk = { workspace = true, default-features = false, features = ["wallet", "nostr"] }
|
cdk = { workspace = true, default-features = false, features = ["wallet"] }
|
||||||
cdk-redb = { workspace = true, default-features = false, features = ["wallet", "nostr"] }
|
cdk-redb = { workspace = true, default-features = false, features = ["wallet"] }
|
||||||
cdk-sqlite = { workspace = true, default-features = false, features = ["wallet", "nostr"] }
|
cdk-sqlite = { workspace = true, default-features = false, features = ["wallet"] }
|
||||||
clap = { version = "4.4.8", features = ["derive", "env"] }
|
clap = { version = "4.4.8", features = ["derive", "env"] }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -24,3 +24,7 @@ tracing.workspace = true
|
|||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
|
nostr-sdk = { version = "0.31.0", default-features = false, features = [
|
||||||
|
"nip04",
|
||||||
|
"nip44"
|
||||||
|
]}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -5,9 +6,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use bip39::Mnemonic;
|
use bip39::Mnemonic;
|
||||||
use cdk::cdk_database;
|
|
||||||
use cdk::cdk_database::WalletDatabase;
|
use cdk::cdk_database::WalletDatabase;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
|
use cdk::{cdk_database, UncheckedUrl};
|
||||||
use cdk_redb::RedbWalletDatabase;
|
use cdk_redb::RedbWalletDatabase;
|
||||||
use cdk_sqlite::WalletSQLiteDatabase;
|
use cdk_sqlite::WalletSQLiteDatabase;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
@@ -117,38 +118,63 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let wallet = Wallet::new(localstore, &mnemonic.to_seed_normalized(""), vec![]);
|
let mut wallets: HashMap<UncheckedUrl, Wallet> = HashMap::new();
|
||||||
|
|
||||||
|
let mints = localstore.get_mints().await?;
|
||||||
|
|
||||||
|
for (mint, _) in mints {
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
&mint.to_string(),
|
||||||
|
cdk::nuts::CurrencyUnit::Sat,
|
||||||
|
localstore.clone(),
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
);
|
||||||
|
|
||||||
|
wallets.insert(mint, wallet);
|
||||||
|
}
|
||||||
|
|
||||||
match &args.command {
|
match &args.command {
|
||||||
Commands::DecodeToken(sub_command_args) => {
|
Commands::DecodeToken(sub_command_args) => {
|
||||||
sub_commands::decode_token::decode_token(sub_command_args)
|
sub_commands::decode_token::decode_token(sub_command_args)
|
||||||
}
|
}
|
||||||
Commands::Balance => sub_commands::balance::balance(wallet).await,
|
Commands::Balance => sub_commands::balance::balance(wallets).await,
|
||||||
Commands::Melt(sub_command_args) => {
|
Commands::Melt(sub_command_args) => {
|
||||||
sub_commands::melt::melt(wallet, sub_command_args).await
|
sub_commands::melt::melt(wallets, sub_command_args).await
|
||||||
}
|
}
|
||||||
Commands::Receive(sub_command_args) => {
|
Commands::Receive(sub_command_args) => {
|
||||||
sub_commands::receive::receive(wallet, sub_command_args).await
|
sub_commands::receive::receive(
|
||||||
|
wallets,
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
localstore,
|
||||||
|
sub_command_args,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
Commands::Send(sub_command_args) => {
|
Commands::Send(sub_command_args) => {
|
||||||
sub_commands::send::send(wallet, sub_command_args).await
|
sub_commands::send::send(wallets, sub_command_args).await
|
||||||
}
|
}
|
||||||
Commands::CheckSpendable => sub_commands::check_spent::check_spent(wallet).await,
|
Commands::CheckSpendable => sub_commands::check_spent::check_spent(wallets).await,
|
||||||
Commands::MintInfo(sub_command_args) => {
|
Commands::MintInfo(sub_command_args) => {
|
||||||
sub_commands::mint_info::mint_info(sub_command_args).await
|
sub_commands::mint_info::mint_info(sub_command_args).await
|
||||||
}
|
}
|
||||||
Commands::Mint(sub_command_args) => {
|
Commands::Mint(sub_command_args) => {
|
||||||
sub_commands::mint::mint(wallet, sub_command_args).await
|
sub_commands::mint::mint(
|
||||||
|
wallets,
|
||||||
|
&mnemonic.to_seed_normalized(""),
|
||||||
|
localstore,
|
||||||
|
sub_command_args,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
Commands::PendingMint => sub_commands::pending_mints::pending_mints(wallet).await,
|
Commands::PendingMint => sub_commands::pending_mints::pending_mints(wallets).await,
|
||||||
Commands::Burn(sub_command_args) => {
|
Commands::Burn(sub_command_args) => {
|
||||||
sub_commands::burn::burn(wallet, sub_command_args).await
|
sub_commands::burn::burn(wallets, sub_command_args).await
|
||||||
}
|
}
|
||||||
Commands::Restore(sub_command_args) => {
|
Commands::Restore(sub_command_args) => {
|
||||||
sub_commands::restore::restore(wallet, sub_command_args).await
|
sub_commands::restore::restore(wallets, sub_command_args).await
|
||||||
}
|
}
|
||||||
Commands::UpdateMintUrl(sub_command_args) => {
|
Commands::UpdateMintUrl(sub_command_args) => {
|
||||||
sub_commands::update_mint_url::update_mint_url(wallet, sub_command_args).await
|
sub_commands::update_mint_url::update_mint_url(wallets, sub_command_args).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cdk::nuts::CurrencyUnit;
|
|
||||||
use cdk::url::UncheckedUrl;
|
use cdk::url::UncheckedUrl;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use cdk::Amount;
|
use cdk::Amount;
|
||||||
|
|
||||||
pub async fn balance(wallet: Wallet) -> Result<()> {
|
pub async fn balance(wallets: HashMap<UncheckedUrl, Wallet>) -> Result<()> {
|
||||||
let _ = mint_balances(&wallet).await;
|
mint_balances(wallets).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn mint_balances(
|
pub async fn mint_balances(
|
||||||
wallet: &Wallet,
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
) -> Result<Vec<(UncheckedUrl, HashMap<CurrencyUnit, Amount>)>> {
|
) -> Result<Vec<(Wallet, Amount)>> {
|
||||||
let mints_amounts: Vec<(UncheckedUrl, HashMap<_, _>)> =
|
let mut wallets_vec: Vec<(Wallet, Amount)> = Vec::with_capacity(wallets.capacity());
|
||||||
wallet.mint_balances().await?.into_iter().collect();
|
|
||||||
|
|
||||||
for (i, (mint, balance)) in mints_amounts.iter().enumerate() {
|
for (i, (mint_url, wallet)) in wallets.iter().enumerate() {
|
||||||
println!("{i}: {mint}:");
|
let mint_url = mint_url.clone();
|
||||||
for (unit, amount) in balance {
|
let amount = wallet.total_balance().await?;
|
||||||
println!("- {amount} {unit}");
|
println!("{i}: {mint_url} {amount}");
|
||||||
|
wallets_vec.push((wallet.clone(), amount));
|
||||||
}
|
}
|
||||||
println!("---------");
|
Ok(wallets_vec)
|
||||||
}
|
|
||||||
|
|
||||||
Ok(mints_amounts)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
|
use cdk::{Amount, UncheckedUrl};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct BurnSubCommand {
|
pub struct BurnSubCommand {
|
||||||
/// Mint Url
|
/// Mint Url
|
||||||
mint_url: Option<String>,
|
mint_url: Option<UncheckedUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn burn(wallet: Wallet, sub_command_args: &BurnSubCommand) -> Result<()> {
|
pub async fn burn(
|
||||||
let amount_burnt = wallet
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
.check_all_pending_proofs(sub_command_args.mint_url.clone().map(|u| u.into()), None)
|
sub_command_args: &BurnSubCommand,
|
||||||
.await?;
|
) -> Result<()> {
|
||||||
|
let mut total_burnt = Amount::ZERO;
|
||||||
|
match &sub_command_args.mint_url {
|
||||||
|
Some(mint_url) => {
|
||||||
|
let wallet = wallets.get(mint_url).unwrap();
|
||||||
|
total_burnt = wallet.check_all_pending_proofs().await?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
for wallet in wallets.values() {
|
||||||
|
let amount_burnt = wallet.check_all_pending_proofs().await?;
|
||||||
|
total_burnt += amount_burnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!("{amount_burnt} burned");
|
println!("{total_burnt} burned");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::println;
|
||||||
use std::{io, println};
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use cdk::url::UncheckedUrl;
|
use cdk::url::UncheckedUrl;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
|
|
||||||
pub async fn check_spent(wallet: Wallet) -> Result<()> {
|
pub async fn check_spent(wallets: HashMap<UncheckedUrl, Wallet>) -> Result<()> {
|
||||||
let mints_amounts: Vec<(UncheckedUrl, HashMap<_, _>)> =
|
for wallet in wallets.values() {
|
||||||
wallet.mint_balances().await?.into_iter().collect();
|
let amount = wallet.check_all_pending_proofs().await?;
|
||||||
|
|
||||||
for (i, (mint, amount)) in mints_amounts.iter().enumerate() {
|
println!("Amount marked as spent: {}", amount);
|
||||||
println!("{}: {}, {:?} sats", i, mint, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Enter mint number to create token");
|
|
||||||
|
|
||||||
let mut user_input = String::new();
|
|
||||||
let stdin = io::stdin();
|
|
||||||
io::stdout().flush().unwrap();
|
|
||||||
stdin.read_line(&mut user_input)?;
|
|
||||||
|
|
||||||
let mint_number: usize = user_input.trim().parse()?;
|
|
||||||
|
|
||||||
if mint_number.gt(&(mints_amounts.len() - 1)) {
|
|
||||||
bail!("Invalid mint number");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mint_url = mints_amounts[mint_number].0.clone();
|
|
||||||
|
|
||||||
let proofs = wallet.get_proofs(mint_url.clone()).await?.unwrap();
|
|
||||||
|
|
||||||
let send_proofs = wallet.check_proofs_spent(mint_url, proofs.to_vec()).await?;
|
|
||||||
|
|
||||||
for proof in send_proofs {
|
|
||||||
println!("{:#?}", proof);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{io, println};
|
use std::{io, println};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use cdk::amount::SplitTarget;
|
use cdk::amount::SplitTarget;
|
||||||
use cdk::nuts::CurrencyUnit;
|
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use cdk::Bolt11Invoice;
|
use cdk::{Bolt11Invoice, UncheckedUrl};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::sub_commands::balance::mint_balances;
|
use crate::sub_commands::balance::mint_balances;
|
||||||
@@ -14,8 +14,11 @@ use crate::sub_commands::balance::mint_balances;
|
|||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct MeltSubCommand {}
|
pub struct MeltSubCommand {}
|
||||||
|
|
||||||
pub async fn melt(wallet: Wallet, _sub_command_args: &MeltSubCommand) -> Result<()> {
|
pub async fn melt(
|
||||||
let mints_amounts = mint_balances(&wallet).await?;
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
|
_sub_command_args: &MeltSubCommand,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mints_amounts = mint_balances(wallets).await?;
|
||||||
|
|
||||||
println!("Enter mint number to create token");
|
println!("Enter mint number to create token");
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ pub async fn melt(wallet: Wallet, _sub_command_args: &MeltSubCommand) -> Result<
|
|||||||
bail!("Invalid mint number");
|
bail!("Invalid mint number");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mint_url = mints_amounts[mint_number].0.clone();
|
let wallet = mints_amounts[mint_number].0.clone();
|
||||||
|
|
||||||
println!("Enter bolt11 invoice request");
|
println!("Enter bolt11 invoice request");
|
||||||
|
|
||||||
@@ -43,26 +46,14 @@ pub async fn melt(wallet: Wallet, _sub_command_args: &MeltSubCommand) -> Result<
|
|||||||
if bolt11
|
if bolt11
|
||||||
.amount_milli_satoshis()
|
.amount_milli_satoshis()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.gt(&(<cdk::Amount as Into<u64>>::into(
|
.gt(&(<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * 1000_u64))
|
||||||
*mints_amounts[mint_number]
|
|
||||||
.1
|
|
||||||
.get(&CurrencyUnit::Sat)
|
|
||||||
.unwrap(),
|
|
||||||
) * 1000_u64))
|
|
||||||
{
|
{
|
||||||
bail!("Not enough funds");
|
bail!("Not enough funds");
|
||||||
}
|
}
|
||||||
let quote = wallet
|
let quote = wallet.melt_quote(bolt11.to_string(), None).await?;
|
||||||
.melt_quote(
|
|
||||||
mint_url.clone(),
|
|
||||||
cdk::nuts::CurrencyUnit::Sat,
|
|
||||||
bolt11.to_string(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let melt = wallet
|
let melt = wallet
|
||||||
.melt(&mint_url, "e.id, SplitTarget::default())
|
.melt("e.id, SplitTarget::default())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cdk::amount::SplitTarget;
|
use cdk::amount::SplitTarget;
|
||||||
|
use cdk::cdk_database::{Error, WalletDatabase};
|
||||||
use cdk::nuts::CurrencyUnit;
|
use cdk::nuts::CurrencyUnit;
|
||||||
use cdk::url::UncheckedUrl;
|
use cdk::url::UncheckedUrl;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
@@ -19,15 +22,20 @@ pub struct MintSubCommand {
|
|||||||
unit: String,
|
unit: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn mint(wallet: Wallet, sub_command_args: &MintSubCommand) -> Result<()> {
|
pub async fn mint(
|
||||||
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
|
seed: &[u8],
|
||||||
|
localstore: Arc<dyn WalletDatabase<Err = Error> + Sync + Send>,
|
||||||
|
sub_command_args: &MintSubCommand,
|
||||||
|
) -> Result<()> {
|
||||||
let mint_url = sub_command_args.mint_url.clone();
|
let mint_url = sub_command_args.mint_url.clone();
|
||||||
|
let wallet = match wallets.get(&mint_url) {
|
||||||
|
Some(wallet) => wallet.clone(),
|
||||||
|
None => Wallet::new(&mint_url.to_string(), CurrencyUnit::Sat, localstore, seed),
|
||||||
|
};
|
||||||
|
|
||||||
let quote = wallet
|
let quote = wallet
|
||||||
.mint_quote(
|
.mint_quote(Amount::from(sub_command_args.amount))
|
||||||
mint_url.clone(),
|
|
||||||
CurrencyUnit::from(&sub_command_args.unit),
|
|
||||||
Amount::from(sub_command_args.amount),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("Quote: {:#?}", quote);
|
println!("Quote: {:#?}", quote);
|
||||||
@@ -35,9 +43,7 @@ pub async fn mint(wallet: Wallet, sub_command_args: &MintSubCommand) -> Result<(
|
|||||||
println!("Please pay: {}", quote.request);
|
println!("Please pay: {}", quote.request);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let status = wallet
|
let status = wallet.mint_quote_status("e.id).await?;
|
||||||
.mint_quote_status(mint_url.clone(), "e.id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if status.paid {
|
if status.paid {
|
||||||
break;
|
break;
|
||||||
@@ -46,9 +52,7 @@ pub async fn mint(wallet: Wallet, sub_command_args: &MintSubCommand) -> Result<(
|
|||||||
sleep(Duration::from_secs(2)).await;
|
sleep(Duration::from_secs(2)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let receive_amount = wallet
|
let receive_amount = wallet.mint("e.id, SplitTarget::default(), None).await?;
|
||||||
.mint(mint_url.clone(), "e.id, SplitTarget::default(), None)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
println!("Received {receive_amount} from mint {mint_url}");
|
println!("Received {receive_amount} from mint {mint_url}");
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
|
use cdk::{Amount, UncheckedUrl};
|
||||||
|
|
||||||
pub async fn pending_mints(wallet: Wallet) -> Result<()> {
|
pub async fn pending_mints(wallets: HashMap<UncheckedUrl, Wallet>) -> Result<()> {
|
||||||
let amount_claimed = wallet.check_all_mint_quotes().await?;
|
let mut amount_claimed = Amount::ZERO;
|
||||||
|
for wallet in wallets.values() {
|
||||||
|
let claimed = wallet.check_all_mint_quotes().await?;
|
||||||
|
amount_claimed += claimed;
|
||||||
|
}
|
||||||
|
|
||||||
println!("Amount minted: {amount_claimed}");
|
println!("Amount minted: {amount_claimed}");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cdk::amount::SplitTarget;
|
use cdk::amount::SplitTarget;
|
||||||
use cdk::nuts::SecretKey;
|
use cdk::cdk_database::{Error, WalletDatabase};
|
||||||
|
use cdk::nuts::{CurrencyUnit, SecretKey, Token};
|
||||||
|
use cdk::util::unix_time;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
|
use cdk::{Amount, UncheckedUrl};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use nostr_sdk::nips::nip04;
|
||||||
|
use nostr_sdk::{Filter, Keys, Kind, Timestamp};
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct ReceiveSubCommand {
|
pub struct ReceiveSubCommand {
|
||||||
@@ -27,54 +34,82 @@ pub struct ReceiveSubCommand {
|
|||||||
preimage: Vec<String>,
|
preimage: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive(wallet: Wallet, sub_command_args: &ReceiveSubCommand) -> Result<()> {
|
pub async fn receive(
|
||||||
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
|
seed: &[u8],
|
||||||
|
localstore: Arc<dyn WalletDatabase<Err = Error> + Sync + Send>,
|
||||||
|
sub_command_args: &ReceiveSubCommand,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut signing_keys = Vec::new();
|
||||||
|
|
||||||
|
if !sub_command_args.signing_key.is_empty() {
|
||||||
|
let mut s_keys: Vec<SecretKey> = sub_command_args
|
||||||
|
.signing_key
|
||||||
|
.iter()
|
||||||
|
.map(|s| SecretKey::from_str(s).unwrap())
|
||||||
|
.collect();
|
||||||
|
signing_keys.append(&mut s_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount = match &sub_command_args.token {
|
||||||
|
Some(token_str) => {
|
||||||
|
receive_token(
|
||||||
|
token_str,
|
||||||
|
wallets,
|
||||||
|
seed,
|
||||||
|
&localstore,
|
||||||
|
&signing_keys,
|
||||||
|
&sub_command_args.preimage,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
//wallet.add_p2pk_signing_key(nostr_signing_key).await;
|
||||||
let nostr_key = match sub_command_args.nostr_key.as_ref() {
|
let nostr_key = match sub_command_args.nostr_key.as_ref() {
|
||||||
Some(nostr_key) => {
|
Some(nostr_key) => {
|
||||||
let secret_key = SecretKey::from_str(nostr_key)?;
|
let secret_key = SecretKey::from_str(nostr_key)?;
|
||||||
wallet.add_p2pk_signing_key(secret_key.clone()).await;
|
|
||||||
Some(secret_key)
|
Some(secret_key)
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !sub_command_args.signing_key.is_empty() {
|
let nostr_key =
|
||||||
let signing_keys: Vec<SecretKey> = sub_command_args
|
nostr_key.ok_or(anyhow!("Nostr key required if token is not provided"))?;
|
||||||
.signing_key
|
|
||||||
.iter()
|
|
||||||
.map(|s| SecretKey::from_str(s).unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for signing_key in signing_keys {
|
signing_keys.push(nostr_key.clone());
|
||||||
wallet.add_p2pk_signing_key(signing_key).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let preimage = match sub_command_args.preimage.is_empty() {
|
let relays = sub_command_args.relay.clone();
|
||||||
true => None,
|
let since = localstore
|
||||||
false => Some(sub_command_args.preimage.clone()),
|
.get_nostr_last_checked(&nostr_key.public_key())
|
||||||
};
|
|
||||||
|
|
||||||
let amount = match nostr_key {
|
|
||||||
Some(nostr_key) => {
|
|
||||||
assert!(!sub_command_args.relay.is_empty());
|
|
||||||
wallet
|
|
||||||
.add_nostr_relays(sub_command_args.relay.clone())
|
|
||||||
.await?;
|
.await?;
|
||||||
wallet
|
|
||||||
.nostr_receive(nostr_key, sub_command_args.since, SplitTarget::default())
|
let tokens = nostr_receive(relays, nostr_key.clone(), since).await?;
|
||||||
.await?
|
|
||||||
}
|
let mut total_amount = Amount::ZERO;
|
||||||
None => {
|
for token_str in &tokens {
|
||||||
wallet
|
match receive_token(
|
||||||
.receive(
|
token_str,
|
||||||
sub_command_args
|
wallets.clone(),
|
||||||
.token
|
seed,
|
||||||
.as_ref()
|
&localstore,
|
||||||
.ok_or(anyhow!("Token Required"))?,
|
&signing_keys,
|
||||||
&SplitTarget::default(),
|
&sub_command_args.preimage,
|
||||||
preimage,
|
|
||||||
)
|
)
|
||||||
.await?
|
.await
|
||||||
|
{
|
||||||
|
Ok(amount) => {
|
||||||
|
total_amount += amount;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localstore
|
||||||
|
.add_nostr_last_checked(nostr_key.public_key(), unix_time() as u32)
|
||||||
|
.await?;
|
||||||
|
total_amount
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -82,3 +117,82 @@ pub async fn receive(wallet: Wallet, sub_command_args: &ReceiveSubCommand) -> Re
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn receive_token(
|
||||||
|
token_str: &str,
|
||||||
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
|
seed: &[u8],
|
||||||
|
localstore: &Arc<dyn WalletDatabase<Err = Error> + Sync + Send>,
|
||||||
|
signing_keys: &[SecretKey],
|
||||||
|
preimage: &[String],
|
||||||
|
) -> Result<Amount> {
|
||||||
|
let token = Token::from_str(token_str)?;
|
||||||
|
let mint_url = token.token.first().unwrap().mint.clone();
|
||||||
|
|
||||||
|
let wallet = match wallets.get(&mint_url) {
|
||||||
|
Some(wallet) => wallet.clone(),
|
||||||
|
None => Wallet::new(
|
||||||
|
&mint_url.to_string(),
|
||||||
|
CurrencyUnit::Sat,
|
||||||
|
Arc::clone(localstore),
|
||||||
|
seed,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let amount = wallet
|
||||||
|
.receive(token_str, &SplitTarget::default(), signing_keys, preimage)
|
||||||
|
.await?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive tokens sent to nostr pubkey via dm
|
||||||
|
async fn nostr_receive(
|
||||||
|
relays: Vec<String>,
|
||||||
|
nostr_signing_key: SecretKey,
|
||||||
|
since: Option<u32>,
|
||||||
|
) -> Result<HashSet<String>> {
|
||||||
|
let verifying_key = nostr_signing_key.public_key();
|
||||||
|
|
||||||
|
let x_only_pubkey = verifying_key.x_only_public_key();
|
||||||
|
|
||||||
|
let nostr_pubkey = nostr_sdk::PublicKey::from_hex(x_only_pubkey.to_string())?;
|
||||||
|
|
||||||
|
let since = since.map(|s| Timestamp::from(s as u64));
|
||||||
|
|
||||||
|
let filter = match since {
|
||||||
|
Some(since) => Filter::new()
|
||||||
|
.pubkey(nostr_pubkey)
|
||||||
|
.kind(Kind::EncryptedDirectMessage)
|
||||||
|
.since(since),
|
||||||
|
None => Filter::new()
|
||||||
|
.pubkey(nostr_pubkey)
|
||||||
|
.kind(Kind::EncryptedDirectMessage),
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = nostr_sdk::Client::default();
|
||||||
|
|
||||||
|
client.add_relays(relays).await?;
|
||||||
|
|
||||||
|
client.connect().await;
|
||||||
|
|
||||||
|
let events = client.get_events_of(vec![filter], None).await?;
|
||||||
|
|
||||||
|
let mut tokens: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
let keys = Keys::from_str(&(nostr_signing_key).to_secret_hex())?;
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
if event.kind() == Kind::EncryptedDirectMessage {
|
||||||
|
if let Ok(msg) = nip04::decrypt(keys.secret_key()?, event.author_ref(), event.content())
|
||||||
|
{
|
||||||
|
if let Some(token) = cdk::wallet::util::token_from_text(&msg) {
|
||||||
|
tokens.insert(token.to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::error!("Impossible to decrypt direct message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use anyhow::Result;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use cdk::url::UncheckedUrl;
|
use cdk::url::UncheckedUrl;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
@@ -9,10 +11,15 @@ pub struct RestoreSubCommand {
|
|||||||
mint_url: UncheckedUrl,
|
mint_url: UncheckedUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore(wallet: Wallet, sub_command_args: &RestoreSubCommand) -> Result<()> {
|
pub async fn restore(
|
||||||
let mint_url = sub_command_args.mint_url.clone();
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
|
sub_command_args: &RestoreSubCommand,
|
||||||
|
) -> Result<()> {
|
||||||
|
let wallet = wallets
|
||||||
|
.get(&sub_command_args.mint_url)
|
||||||
|
.ok_or(anyhow!("Unknown mint url"))?;
|
||||||
|
|
||||||
let amount = wallet.restore(mint_url).await?;
|
let amount = wallet.restore().await?;
|
||||||
|
|
||||||
println!("Restored {}", amount);
|
println!("Restored {}", amount);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{io, println};
|
use std::{io, println};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use cdk::amount::SplitTarget;
|
use cdk::amount::SplitTarget;
|
||||||
use cdk::nuts::{Conditions, CurrencyUnit, PublicKey, SpendingConditions};
|
use cdk::nuts::{Conditions, PublicKey, SpendingConditions};
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use cdk::Amount;
|
use cdk::{Amount, UncheckedUrl};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::sub_commands::balance::mint_balances;
|
use crate::sub_commands::balance::mint_balances;
|
||||||
@@ -33,8 +34,11 @@ pub struct SendSubCommand {
|
|||||||
refund_keys: Vec<String>,
|
refund_keys: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(wallet: Wallet, sub_command_args: &SendSubCommand) -> Result<()> {
|
pub async fn send(
|
||||||
let mints_amounts = mint_balances(&wallet).await?;
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
|
sub_command_args: &SendSubCommand,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mints_amounts = mint_balances(wallets).await?;
|
||||||
|
|
||||||
println!("Enter mint number to create token");
|
println!("Enter mint number to create token");
|
||||||
|
|
||||||
@@ -49,8 +53,6 @@ pub async fn send(wallet: Wallet, sub_command_args: &SendSubCommand) -> Result<(
|
|||||||
bail!("Invalid mint number");
|
bail!("Invalid mint number");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mint_url = mints_amounts[mint_number].0.clone();
|
|
||||||
|
|
||||||
println!("Enter value of token in sats");
|
println!("Enter value of token in sats");
|
||||||
|
|
||||||
let mut user_input = String::new();
|
let mut user_input = String::new();
|
||||||
@@ -59,11 +61,7 @@ pub async fn send(wallet: Wallet, sub_command_args: &SendSubCommand) -> Result<(
|
|||||||
stdin.read_line(&mut user_input)?;
|
stdin.read_line(&mut user_input)?;
|
||||||
let token_amount = Amount::from(user_input.trim().parse::<u64>()?);
|
let token_amount = Amount::from(user_input.trim().parse::<u64>()?);
|
||||||
|
|
||||||
if token_amount.gt(mints_amounts[mint_number]
|
if token_amount.gt(&mints_amounts[mint_number].1) {
|
||||||
.1
|
|
||||||
.get(&CurrencyUnit::Sat)
|
|
||||||
.unwrap())
|
|
||||||
{
|
|
||||||
bail!("Not enough funds");
|
bail!("Not enough funds");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +143,10 @@ pub async fn send(wallet: Wallet, sub_command_args: &SendSubCommand) -> Result<(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let wallet = mints_amounts[mint_number].0.clone();
|
||||||
|
|
||||||
let token = wallet
|
let token = wallet
|
||||||
.send(
|
.send(
|
||||||
&mint_url,
|
|
||||||
CurrencyUnit::Sat,
|
|
||||||
token_amount,
|
token_amount,
|
||||||
sub_command_args.memo.clone(),
|
sub_command_args.memo.clone(),
|
||||||
conditions,
|
conditions,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use anyhow::Result;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use cdk::url::UncheckedUrl;
|
use cdk::url::UncheckedUrl;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
@@ -12,7 +14,7 @@ pub struct UpdateMintUrlSubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_mint_url(
|
pub async fn update_mint_url(
|
||||||
wallet: Wallet,
|
wallets: HashMap<UncheckedUrl, Wallet>,
|
||||||
sub_command_args: &UpdateMintUrlSubCommand,
|
sub_command_args: &UpdateMintUrlSubCommand,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let UpdateMintUrlSubCommand {
|
let UpdateMintUrlSubCommand {
|
||||||
@@ -20,9 +22,12 @@ pub async fn update_mint_url(
|
|||||||
new_mint_url,
|
new_mint_url,
|
||||||
} = sub_command_args;
|
} = sub_command_args;
|
||||||
|
|
||||||
wallet
|
let mut wallet = wallets
|
||||||
.update_mint_url(old_mint_url.clone(), new_mint_url.clone())
|
.get(old_mint_url)
|
||||||
.await?;
|
.ok_or(anyhow!("Unknown mint url"))?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
wallet.update_mint_url(new_mint_url.clone()).await?;
|
||||||
|
|
||||||
println!("Mint Url changed from {} to {}", old_mint_url, new_mint_url);
|
println!("Mint Url changed from {} to {}", old_mint_url, new_mint_url);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ rust-version.workspace = true
|
|||||||
default = ["mint", "wallet"]
|
default = ["mint", "wallet"]
|
||||||
mint = ["cdk/mint"]
|
mint = ["cdk/mint"]
|
||||||
wallet = ["cdk/wallet"]
|
wallet = ["cdk/wallet"]
|
||||||
nostr = ["cdk/nostr"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ const MINT_KEYS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_
|
|||||||
const PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("proofs");
|
const PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("proofs");
|
||||||
const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
|
const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
|
||||||
const KEYSET_COUNTER: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
|
const KEYSET_COUNTER: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
const NOSTR_LAST_CHECKED: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
|
const NOSTR_LAST_CHECKED: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
|
||||||
|
|
||||||
const DATABASE_VERSION: u32 = 0;
|
const DATABASE_VERSION: u32 = 0;
|
||||||
@@ -73,7 +72,6 @@ impl RedbWalletDatabase {
|
|||||||
let _ = write_txn.open_table(MINT_KEYS_TABLE)?;
|
let _ = write_txn.open_table(MINT_KEYS_TABLE)?;
|
||||||
let _ = write_txn.open_table(PROOFS_TABLE)?;
|
let _ = write_txn.open_table(PROOFS_TABLE)?;
|
||||||
let _ = write_txn.open_table(KEYSET_COUNTER)?;
|
let _ = write_txn.open_table(KEYSET_COUNTER)?;
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
let _ = write_txn.open_table(NOSTR_LAST_CHECKED)?;
|
let _ = write_txn.open_table(NOSTR_LAST_CHECKED)?;
|
||||||
table.insert("db_version", "0")?;
|
table.insert("db_version", "0")?;
|
||||||
}
|
}
|
||||||
@@ -655,7 +653,6 @@ impl WalletDatabase for RedbWalletDatabase {
|
|||||||
Ok(counter.map(|c| c.value()))
|
Ok(counter.map(|c| c.value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn get_nostr_last_checked(
|
async fn get_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
@@ -673,7 +670,6 @@ impl WalletDatabase for RedbWalletDatabase {
|
|||||||
|
|
||||||
Ok(last_checked.map(|c| c.value()))
|
Ok(last_checked.map(|c| c.value()))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn add_nostr_last_checked(
|
async fn add_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ const MELT_QUOTES: &str = "melt_quotes";
|
|||||||
const PROOFS: &str = "proofs";
|
const PROOFS: &str = "proofs";
|
||||||
const CONFIG: &str = "config";
|
const CONFIG: &str = "config";
|
||||||
const KEYSET_COUNTER: &str = "keyset_counter";
|
const KEYSET_COUNTER: &str = "keyset_counter";
|
||||||
|
const NOSTR_LAST_CHECKED: &str = "nostr_last_check";
|
||||||
|
|
||||||
const DATABASE_VERSION: u32 = 2;
|
const DATABASE_VERSION: u32 = 3;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@@ -87,6 +88,7 @@ impl RexieWalletDatabase {
|
|||||||
.add_object_store(ObjectStore::new(MELT_QUOTES))
|
.add_object_store(ObjectStore::new(MELT_QUOTES))
|
||||||
.add_object_store(ObjectStore::new(CONFIG))
|
.add_object_store(ObjectStore::new(CONFIG))
|
||||||
.add_object_store(ObjectStore::new(KEYSET_COUNTER))
|
.add_object_store(ObjectStore::new(KEYSET_COUNTER))
|
||||||
|
.add_object_store(ObjectStore::new(NOSTR_LAST_CHECKED))
|
||||||
// Build the database
|
// Build the database
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
@@ -712,4 +714,55 @@ impl WalletDatabase for RexieWalletDatabase {
|
|||||||
|
|
||||||
Ok(current_count)
|
Ok(current_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn add_nostr_last_checked(
|
||||||
|
&self,
|
||||||
|
verifying_key: PublicKey,
|
||||||
|
last_checked: u32,
|
||||||
|
) -> Result<(), Self::Err> {
|
||||||
|
let rexie = self.db.lock().await;
|
||||||
|
|
||||||
|
let transaction = rexie
|
||||||
|
.transaction(&[NOSTR_LAST_CHECKED], TransactionMode::ReadWrite)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
let counter_store = transaction.store(NOSTR_LAST_CHECKED).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let verifying_key = serde_wasm_bindgen::to_value(&verifying_key).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let last_checked = serde_wasm_bindgen::to_value(&last_checked).map_err(Error::from)?;
|
||||||
|
|
||||||
|
counter_store
|
||||||
|
.put(&last_checked, Some(&verifying_key))
|
||||||
|
.await
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
transaction.done().await.map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_nostr_last_checked(
|
||||||
|
&self,
|
||||||
|
verifying_key: &PublicKey,
|
||||||
|
) -> Result<Option<u32>, Self::Err> {
|
||||||
|
let rexie = self.db.lock().await;
|
||||||
|
|
||||||
|
let transaction = rexie
|
||||||
|
.transaction(&[NOSTR_LAST_CHECKED], TransactionMode::ReadOnly)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
let nostr_last_check_store = transaction.store(NOSTR_LAST_CHECKED).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let verifying_key = serde_wasm_bindgen::to_value(verifying_key).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let last_checked = nostr_last_check_store
|
||||||
|
.get(&verifying_key)
|
||||||
|
.await
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
let last_checked: Option<u32> =
|
||||||
|
serde_wasm_bindgen::from_value(last_checked).map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(last_checked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ rust-version.workspace = true
|
|||||||
default = ["mint", "wallet"]
|
default = ["mint", "wallet"]
|
||||||
mint = ["cdk/mint"]
|
mint = ["cdk/mint"]
|
||||||
wallet = ["cdk/wallet"]
|
wallet = ["cdk/wallet"]
|
||||||
nostr = ["cdk/nostr"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitcoin.workspace = true
|
bitcoin.workspace = true
|
||||||
|
|||||||
@@ -627,7 +627,6 @@ WHERE id=?;
|
|||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
async fn get_nostr_last_checked(
|
async fn get_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
verifying_key: &PublicKey,
|
verifying_key: &PublicKey,
|
||||||
@@ -656,7 +655,6 @@ WHERE key=?;
|
|||||||
|
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
async fn add_nostr_last_checked(
|
async fn add_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
verifying_key: PublicKey,
|
verifying_key: PublicKey,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ license.workspace = true
|
|||||||
default = ["mint", "wallet"]
|
default = ["mint", "wallet"]
|
||||||
mint = []
|
mint = []
|
||||||
wallet = ["dep:reqwest"]
|
wallet = ["dep:reqwest"]
|
||||||
nostr = ["dep:nostr-sdk"]
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -42,10 +41,6 @@ tracing = { version = "0.1", default-features = false, features = [
|
|||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
url = "2.3"
|
url = "2.3"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
nostr-sdk = { version = "0.31.0", default-features = false, features = [
|
|
||||||
"nip04",
|
|
||||||
"nip44"
|
|
||||||
], optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tokio = { workspace = true, features = [
|
tokio = { workspace = true, features = [
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -7,7 +6,7 @@ use cdk::cdk_database::WalletMemoryDatabase;
|
|||||||
use cdk::error::Error;
|
use cdk::error::Error;
|
||||||
use cdk::nuts::CurrencyUnit;
|
use cdk::nuts::CurrencyUnit;
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use cdk::{Amount, UncheckedUrl};
|
use cdk::Amount;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
@@ -16,24 +15,18 @@ async fn main() -> Result<(), Error> {
|
|||||||
let localstore = WalletMemoryDatabase::default();
|
let localstore = WalletMemoryDatabase::default();
|
||||||
let seed = rand::thread_rng().gen::<[u8; 32]>();
|
let seed = rand::thread_rng().gen::<[u8; 32]>();
|
||||||
|
|
||||||
let mint_url = UncheckedUrl::from_str("https://testnut.cashu.space").unwrap();
|
let mint_url = "https://testnut.cashu.space";
|
||||||
let unit = CurrencyUnit::Sat;
|
let unit = CurrencyUnit::Sat;
|
||||||
let amount = Amount::from(10);
|
let amount = Amount::from(10);
|
||||||
|
|
||||||
let wallet = Wallet::new(Arc::new(localstore), &seed, vec![]);
|
let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed);
|
||||||
|
|
||||||
let quote = wallet
|
let quote = wallet.mint_quote(amount).await.unwrap();
|
||||||
.mint_quote(mint_url.clone(), unit.clone(), amount)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("Quote: {:#?}", quote);
|
println!("Quote: {:#?}", quote);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let status = wallet
|
let status = wallet.mint_quote_status("e.id).await.unwrap();
|
||||||
.mint_quote_status(mint_url.clone(), "e.id)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("Quote status: {}", status.paid);
|
println!("Quote status: {}", status.paid);
|
||||||
|
|
||||||
@@ -45,14 +38,14 @@ async fn main() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let receive_amount = wallet
|
let receive_amount = wallet
|
||||||
.mint(mint_url.clone(), "e.id, SplitTarget::default(), None)
|
.mint("e.id, SplitTarget::default(), None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
println!("Received {receive_amount} from mint {mint_url}");
|
println!("Received {receive_amount} from mint {mint_url}");
|
||||||
|
|
||||||
let token = wallet
|
let token = wallet
|
||||||
.send(&mint_url, unit, amount, None, None, &SplitTarget::default())
|
.send(amount, None, None, &SplitTarget::default())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -7,7 +6,7 @@ use cdk::cdk_database::WalletMemoryDatabase;
|
|||||||
use cdk::error::Error;
|
use cdk::error::Error;
|
||||||
use cdk::nuts::{CurrencyUnit, SecretKey, SpendingConditions};
|
use cdk::nuts::{CurrencyUnit, SecretKey, SpendingConditions};
|
||||||
use cdk::wallet::Wallet;
|
use cdk::wallet::Wallet;
|
||||||
use cdk::{Amount, UncheckedUrl};
|
use cdk::Amount;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
@@ -16,24 +15,18 @@ async fn main() -> Result<(), Error> {
|
|||||||
let localstore = WalletMemoryDatabase::default();
|
let localstore = WalletMemoryDatabase::default();
|
||||||
let seed = rand::thread_rng().gen::<[u8; 32]>();
|
let seed = rand::thread_rng().gen::<[u8; 32]>();
|
||||||
|
|
||||||
let mint_url = UncheckedUrl::from_str("https://testnut.cashu.space").unwrap();
|
let mint_url = "https://testnut.cashu.space";
|
||||||
let unit = CurrencyUnit::Sat;
|
let unit = CurrencyUnit::Sat;
|
||||||
let amount = Amount::from(10);
|
let amount = Amount::from(10);
|
||||||
|
|
||||||
let wallet = Wallet::new(Arc::new(localstore), &seed, vec![]);
|
let wallet = Wallet::new(mint_url, unit.clone(), Arc::new(localstore), &seed);
|
||||||
|
|
||||||
let quote = wallet
|
let quote = wallet.mint_quote(amount).await.unwrap();
|
||||||
.mint_quote(mint_url.clone(), unit.clone(), amount)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("Minting nuts ...");
|
println!("Minting nuts ...");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let status = wallet
|
let status = wallet.mint_quote_status("e.id).await.unwrap();
|
||||||
.mint_quote_status(mint_url.clone(), "e.id)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("Quote status: {}", status.paid);
|
println!("Quote status: {}", status.paid);
|
||||||
|
|
||||||
@@ -45,7 +38,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _receive_amount = wallet
|
let _receive_amount = wallet
|
||||||
.mint(mint_url.clone(), "e.id, SplitTarget::default(), None)
|
.mint("e.id, SplitTarget::default(), None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -54,24 +47,15 @@ async fn main() -> Result<(), Error> {
|
|||||||
let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
|
let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
|
||||||
|
|
||||||
let token = wallet
|
let token = wallet
|
||||||
.send(
|
.send(amount, None, Some(spending_conditions), &SplitTarget::None)
|
||||||
&mint_url,
|
|
||||||
unit,
|
|
||||||
amount,
|
|
||||||
None,
|
|
||||||
Some(spending_conditions),
|
|
||||||
&SplitTarget::None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
println!("Created token locked to pubkey: {}", secret.public_key());
|
println!("Created token locked to pubkey: {}", secret.public_key());
|
||||||
println!("{}", token);
|
println!("{}", token);
|
||||||
|
|
||||||
wallet.add_p2pk_signing_key(secret).await;
|
|
||||||
|
|
||||||
let amount = wallet
|
let amount = wallet
|
||||||
.receive(&token, &SplitTarget::default(), None)
|
.receive(&token, &SplitTarget::default(), &[secret], &[])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -104,12 +104,10 @@ pub trait WalletDatabase: Debug {
|
|||||||
async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>;
|
async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>;
|
||||||
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err>;
|
async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err>;
|
||||||
|
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
async fn get_nostr_last_checked(
|
async fn get_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
verifying_key: &PublicKey,
|
verifying_key: &PublicKey,
|
||||||
) -> Result<Option<u32>, Self::Err>;
|
) -> Result<Option<u32>, Self::Err>;
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
async fn add_nostr_last_checked(
|
async fn add_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
verifying_key: PublicKey,
|
verifying_key: PublicKey,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ pub struct WalletMemoryDatabase {
|
|||||||
mint_keys: Arc<RwLock<HashMap<Id, Keys>>>,
|
mint_keys: Arc<RwLock<HashMap<Id, Keys>>>,
|
||||||
proofs: Arc<RwLock<HashMap<PublicKey, ProofInfo>>>,
|
proofs: Arc<RwLock<HashMap<PublicKey, ProofInfo>>>,
|
||||||
keyset_counter: Arc<RwLock<HashMap<Id, u32>>>,
|
keyset_counter: Arc<RwLock<HashMap<Id, u32>>>,
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
nostr_last_checked: Arc<RwLock<HashMap<PublicKey, u32>>>,
|
nostr_last_checked: Arc<RwLock<HashMap<PublicKey, u32>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ impl WalletMemoryDatabase {
|
|||||||
melt_quotes: Vec<MeltQuote>,
|
melt_quotes: Vec<MeltQuote>,
|
||||||
mint_keys: Vec<Keys>,
|
mint_keys: Vec<Keys>,
|
||||||
keyset_counter: HashMap<Id, u32>,
|
keyset_counter: HashMap<Id, u32>,
|
||||||
#[cfg(feature = "nostr")] nostr_last_checked: HashMap<PublicKey, u32>,
|
nostr_last_checked: HashMap<PublicKey, u32>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mints: Arc::new(RwLock::new(HashMap::new())),
|
mints: Arc::new(RwLock::new(HashMap::new())),
|
||||||
@@ -52,7 +51,6 @@ impl WalletMemoryDatabase {
|
|||||||
)),
|
)),
|
||||||
proofs: Arc::new(RwLock::new(HashMap::new())),
|
proofs: Arc::new(RwLock::new(HashMap::new())),
|
||||||
keyset_counter: Arc::new(RwLock::new(keyset_counter)),
|
keyset_counter: Arc::new(RwLock::new(keyset_counter)),
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
nostr_last_checked: Arc::new(RwLock::new(nostr_last_checked)),
|
nostr_last_checked: Arc::new(RwLock::new(nostr_last_checked)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,7 +319,6 @@ impl WalletDatabase for WalletMemoryDatabase {
|
|||||||
Ok(self.keyset_counter.read().await.get(id).cloned())
|
Ok(self.keyset_counter.read().await.get(id).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
async fn get_nostr_last_checked(
|
async fn get_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
verifying_key: &PublicKey,
|
verifying_key: &PublicKey,
|
||||||
@@ -333,7 +330,6 @@ impl WalletDatabase for WalletMemoryDatabase {
|
|||||||
.get(verifying_key)
|
.get(verifying_key)
|
||||||
.cloned())
|
.cloned())
|
||||||
}
|
}
|
||||||
#[cfg(feature = "nostr")]
|
|
||||||
async fn add_nostr_last_checked(
|
async fn add_nostr_last_checked(
|
||||||
&self,
|
&self,
|
||||||
verifying_key: PublicKey,
|
verifying_key: PublicKey,
|
||||||
|
|||||||
@@ -63,6 +63,15 @@ pub enum Error {
|
|||||||
/// Unknown error response
|
/// Unknown error response
|
||||||
#[error("Unknown Error response: `{0}`")]
|
#[error("Unknown Error response: `{0}`")]
|
||||||
UnknownErrorResponse(String),
|
UnknownErrorResponse(String),
|
||||||
|
/// Unknown Wallet
|
||||||
|
#[error("Unknown Wallet: `{0}`")]
|
||||||
|
UnknownWallet(String),
|
||||||
|
/// Unknown Wallet
|
||||||
|
#[error("Unknown Wallet: `{0}`")]
|
||||||
|
IncorrectWallet(String),
|
||||||
|
/// Max Fee Ecxeded
|
||||||
|
#[error("Max fee exceeded")]
|
||||||
|
MaxFeeExceeded,
|
||||||
/// CDK Error
|
/// CDK Error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Cashu(#[from] crate::error::Error),
|
Cashu(#[from] crate::error::Error),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
299
crates/cdk/src/wallet/multi_mint_wallet.rs
Normal file
299
crates/cdk/src/wallet/multi_mint_wallet.rs
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
//! MultiMint Wallet
|
||||||
|
//!
|
||||||
|
//! Wrapper around core [`Wallet`] that enables the use of multiple mint unit pairs
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::Error;
|
||||||
|
use crate::amount::SplitTarget;
|
||||||
|
use crate::nuts::{CurrencyUnit, SecretKey, SpendingConditions, Token};
|
||||||
|
use crate::types::{Melted, MintQuote};
|
||||||
|
use crate::{Amount, UncheckedUrl, Wallet};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MultiMintWallet {
|
||||||
|
pub wallets: Arc<Mutex<HashMap<WalletKey, Wallet>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct WalletKey {
|
||||||
|
mint_url: UncheckedUrl,
|
||||||
|
unit: CurrencyUnit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for WalletKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "mint_url: {}, unit: {}", self.mint_url, self.unit,)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletKey {
|
||||||
|
pub fn new(mint_url: UncheckedUrl, unit: CurrencyUnit) -> Self {
|
||||||
|
Self { mint_url, unit }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiMintWallet {
|
||||||
|
/// New Multimint wallet
|
||||||
|
pub fn new(wallets: Vec<Wallet>) -> Self {
|
||||||
|
Self {
|
||||||
|
wallets: Arc::new(Mutex::new(
|
||||||
|
wallets
|
||||||
|
.into_iter()
|
||||||
|
.map(|w| (WalletKey::new(w.mint_url.clone(), w.unit.clone()), w))
|
||||||
|
.collect(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add wallet to MultiMintWallet
|
||||||
|
#[instrument(skip(self, wallet))]
|
||||||
|
pub async fn add_wallet(&self, wallet: Wallet) {
|
||||||
|
let wallet_key = WalletKey::new(wallet.mint_url.clone(), wallet.unit.clone());
|
||||||
|
|
||||||
|
let mut wallets = self.wallets.lock().await;
|
||||||
|
|
||||||
|
wallets.insert(wallet_key, wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove Wallet from MultiMintWallet
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn remove_wallet(&self, wallet_key: &WalletKey) {
|
||||||
|
let mut wallets = self.wallets.lock().await;
|
||||||
|
|
||||||
|
wallets.remove(wallet_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Wallets from MultiMintWallet
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn get_wallets(&self) -> Vec<Wallet> {
|
||||||
|
self.wallets.lock().await.values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Wallet from MultiMintWallet
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn get_wallet(&self, wallet_key: &WalletKey) -> Option<Wallet> {
|
||||||
|
let wallets = self.wallets.lock().await;
|
||||||
|
|
||||||
|
wallets.get(wallet_key).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if mint unit pair is in wallet
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn has(&self, wallet_key: &WalletKey) -> bool {
|
||||||
|
self.wallets.lock().await.contains_key(wallet_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get wallet balances
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn get_balances(
|
||||||
|
&self,
|
||||||
|
unit: &CurrencyUnit,
|
||||||
|
) -> Result<HashMap<UncheckedUrl, Amount>, Error> {
|
||||||
|
let mut balances = HashMap::new();
|
||||||
|
|
||||||
|
for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
|
||||||
|
if unit == u {
|
||||||
|
let wallet_balance = wallet.total_balance().await?;
|
||||||
|
balances.insert(mint_url.clone(), wallet_balance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(balances)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create cashu token
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn send(
|
||||||
|
&self,
|
||||||
|
wallet_key: &WalletKey,
|
||||||
|
amount: Amount,
|
||||||
|
memo: Option<String>,
|
||||||
|
conditions: Option<SpendingConditions>,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.send(amount, memo, conditions, &SplitTarget::default())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mint quote for wallet
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn mint_quote(
|
||||||
|
&self,
|
||||||
|
wallet_key: &WalletKey,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<MintQuote, Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
wallet.mint_quote(amount).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check all mint quotes
|
||||||
|
/// If quote is paid, wallet will mint
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn check_all_mint_quotes(
|
||||||
|
&self,
|
||||||
|
wallet_key: Option<WalletKey>,
|
||||||
|
) -> Result<HashMap<CurrencyUnit, Amount>, Error> {
|
||||||
|
let mut amount_minted = HashMap::new();
|
||||||
|
match wallet_key {
|
||||||
|
Some(wallet_key) => {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(&wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
let amount = wallet.check_all_mint_quotes().await?;
|
||||||
|
amount_minted.insert(wallet.unit.clone(), amount);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
for (_, wallet) in self.wallets.lock().await.iter() {
|
||||||
|
let amount = wallet.check_all_mint_quotes().await?;
|
||||||
|
|
||||||
|
amount_minted
|
||||||
|
.entry(wallet.unit.clone())
|
||||||
|
.and_modify(|b| *b += amount)
|
||||||
|
.or_insert(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(amount_minted)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mint a specific quote
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn mint(
|
||||||
|
&self,
|
||||||
|
wallet_key: &WalletKey,
|
||||||
|
quote_id: &str,
|
||||||
|
conditions: Option<SpendingConditions>,
|
||||||
|
) -> Result<Amount, Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
wallet
|
||||||
|
.mint(quote_id, SplitTarget::default(), conditions)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive token
|
||||||
|
/// Wallet must be already added to multimintwallet
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn receive(
|
||||||
|
&self,
|
||||||
|
encoded_token: &str,
|
||||||
|
p2pk_signing_keys: &[SecretKey],
|
||||||
|
preimages: &[String],
|
||||||
|
) -> Result<Amount, Error> {
|
||||||
|
let token_data = Token::from_str(encoded_token)?;
|
||||||
|
let unit = token_data.unit.unwrap_or_default();
|
||||||
|
let mint_url = token_data.token.first().unwrap().mint.clone();
|
||||||
|
|
||||||
|
let mints: HashSet<&UncheckedUrl> = token_data.token.iter().map(|d| &d.mint).collect();
|
||||||
|
|
||||||
|
// Check that all mints in tokes have wallets
|
||||||
|
for mint in mints {
|
||||||
|
let wallet_key = WalletKey::new(mint.clone(), unit.clone());
|
||||||
|
if !self.has(&wallet_key).await {
|
||||||
|
return Err(Error::UnknownWallet(wallet_key.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet_key = WalletKey::new(mint_url, unit);
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(&wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.receive(
|
||||||
|
encoded_token,
|
||||||
|
&SplitTarget::default(),
|
||||||
|
p2pk_signing_keys,
|
||||||
|
preimages,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pay an bolt11 invoice from specific wallet
|
||||||
|
#[instrument(skip(self, bolt11))]
|
||||||
|
pub async fn pay_invoice_for_wallet(
|
||||||
|
&self,
|
||||||
|
bolt11: &str,
|
||||||
|
wallet_key: &WalletKey,
|
||||||
|
max_fee: Option<Amount>,
|
||||||
|
) -> Result<Melted, Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
let quote = wallet.melt_quote(bolt11.to_string(), None).await?;
|
||||||
|
if let Some(max_fee) = max_fee {
|
||||||
|
if quote.fee_reserve > max_fee {
|
||||||
|
return Err(Error::MaxFeeExceeded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet.melt("e.id, SplitTarget::default()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn restore(&self, wallet_key: &WalletKey) -> Result<Amount, Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
wallet.restore().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify token matches p2pk conditions
|
||||||
|
#[instrument(skip(self, token))]
|
||||||
|
pub async fn verify_token_p2pk(
|
||||||
|
&self,
|
||||||
|
wallet_key: &WalletKey,
|
||||||
|
token: &Token,
|
||||||
|
conditions: SpendingConditions,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
wallet.verify_token_p2pk(token, conditions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifys all proofs in toke have valid dleq proof
|
||||||
|
#[instrument(skip(self, token))]
|
||||||
|
pub async fn verify_token_dleq(
|
||||||
|
&self,
|
||||||
|
wallet_key: &WalletKey,
|
||||||
|
token: &Token,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let wallet = self
|
||||||
|
.get_wallet(wallet_key)
|
||||||
|
.await
|
||||||
|
.ok_or(Error::UnknownWallet(wallet_key.to_string()))?;
|
||||||
|
|
||||||
|
wallet.verify_token_dleq(token).await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
//! Wallet Nostr functions
|
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use nostr_sdk::nips::nip04;
|
|
||||||
use nostr_sdk::{Filter, Timestamp};
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
use super::error::Error;
|
|
||||||
use super::{util, Wallet};
|
|
||||||
use crate::amount::{Amount, SplitTarget};
|
|
||||||
use crate::nuts::SecretKey;
|
|
||||||
|
|
||||||
impl Wallet {
|
|
||||||
/// Add nostr relays to client
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn add_nostr_relays(&self, relays: Vec<String>) -> Result<(), Error> {
|
|
||||||
self.nostr_client.add_relays(relays).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove nostr relays to client
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn remove_nostr_relays(&self, relay: String) -> Result<(), Error> {
|
|
||||||
self.nostr_client.remove_relay(relay).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Nostr relays
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn nostr_relays(&self) -> Vec<String> {
|
|
||||||
self.nostr_client
|
|
||||||
.relays()
|
|
||||||
.await
|
|
||||||
.keys()
|
|
||||||
.map(|url| url.to_string())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Receive tokens sent to nostr pubkey via dm
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub async fn nostr_receive(
|
|
||||||
&self,
|
|
||||||
nostr_signing_key: SecretKey,
|
|
||||||
since: Option<u64>,
|
|
||||||
amount_split_target: SplitTarget,
|
|
||||||
) -> Result<Amount, Error> {
|
|
||||||
use nostr_sdk::{Keys, Kind};
|
|
||||||
|
|
||||||
use crate::util::unix_time;
|
|
||||||
use crate::Amount;
|
|
||||||
|
|
||||||
let verifying_key = nostr_signing_key.public_key();
|
|
||||||
|
|
||||||
let x_only_pubkey = verifying_key.x_only_public_key();
|
|
||||||
|
|
||||||
let nostr_pubkey = nostr_sdk::PublicKey::from_hex(x_only_pubkey.to_string())?;
|
|
||||||
|
|
||||||
let keys = Keys::from_str(&(nostr_signing_key).to_secret_hex())?;
|
|
||||||
self.add_p2pk_signing_key(nostr_signing_key).await;
|
|
||||||
|
|
||||||
let since = match since {
|
|
||||||
Some(since) => Some(Timestamp::from(since)),
|
|
||||||
None => self
|
|
||||||
.localstore
|
|
||||||
.get_nostr_last_checked(&verifying_key)
|
|
||||||
.await?
|
|
||||||
.map(|s| Timestamp::from(s as u64)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let filter = match since {
|
|
||||||
Some(since) => Filter::new()
|
|
||||||
.pubkey(nostr_pubkey)
|
|
||||||
.kind(Kind::EncryptedDirectMessage)
|
|
||||||
.since(since),
|
|
||||||
None => Filter::new()
|
|
||||||
.pubkey(nostr_pubkey)
|
|
||||||
.kind(Kind::EncryptedDirectMessage),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.nostr_client.connect().await;
|
|
||||||
|
|
||||||
let events = self.nostr_client.get_events_of(vec![filter], None).await?;
|
|
||||||
|
|
||||||
let mut tokens: HashSet<String> = HashSet::new();
|
|
||||||
|
|
||||||
for event in events {
|
|
||||||
if event.kind() == Kind::EncryptedDirectMessage {
|
|
||||||
if let Ok(msg) =
|
|
||||||
nip04::decrypt(keys.secret_key()?, event.author_ref(), event.content())
|
|
||||||
{
|
|
||||||
if let Some(token) = util::token_from_text(&msg) {
|
|
||||||
tokens.insert(token.to_string());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::error!("Impossible to decrypt direct message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut total_received = Amount::ZERO;
|
|
||||||
for token in tokens.iter() {
|
|
||||||
match self.receive(token, &amount_split_target, None).await {
|
|
||||||
Ok(amount) => total_received += amount,
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Could not receive token: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.localstore
|
|
||||||
.add_nostr_last_checked(verifying_key, unix_time() as u32)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(total_received)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,7 @@ use crate::nuts::{CurrencyUnit, Proofs, Token};
|
|||||||
use crate::UncheckedUrl;
|
use crate::UncheckedUrl;
|
||||||
|
|
||||||
/// Extract token from text
|
/// Extract token from text
|
||||||
#[cfg(feature = "nostr")]
|
pub fn token_from_text(text: &str) -> Option<&str> {
|
||||||
pub(crate) fn token_from_text(text: &str) -> Option<&str> {
|
|
||||||
let text = text.trim();
|
let text = text.trim();
|
||||||
if let Some(start) = text.find("cashu") {
|
if let Some(start) = text.find("cashu") {
|
||||||
match text[start..].find(' ') {
|
match text[start..].find(' ') {
|
||||||
|
|||||||
@@ -26,11 +26,9 @@ buildargs=(
|
|||||||
"-p cdk"
|
"-p cdk"
|
||||||
"-p cdk --no-default-features"
|
"-p cdk --no-default-features"
|
||||||
"-p cdk --no-default-features --features wallet"
|
"-p cdk --no-default-features --features wallet"
|
||||||
"-p cdk --no-default-features --features wallet --features nostr"
|
|
||||||
"-p cdk --no-default-features --features mint"
|
"-p cdk --no-default-features --features mint"
|
||||||
"-p cdk-redb"
|
"-p cdk-redb"
|
||||||
"-p cdk-redb --no-default-features --features wallet"
|
"-p cdk-redb --no-default-features --features wallet"
|
||||||
"-p cdk-redb --no-default-features --features wallet --features nostr"
|
|
||||||
"-p cdk-redb --no-default-features --features mint"
|
"-p cdk-redb --no-default-features --features mint"
|
||||||
"-p cdk-sqlite --no-default-features --features mint"
|
"-p cdk-sqlite --no-default-features --features mint"
|
||||||
"-p cdk-sqlite --no-default-features --features wallet"
|
"-p cdk-sqlite --no-default-features --features wallet"
|
||||||
|
|||||||
Reference in New Issue
Block a user