Skip to content

Commit 40998da

Browse files
authored
up version, add mint config, add mint repo, add mint client, implement (#521)
1 parent 40120bb commit 40998da

File tree

33 files changed

+773
-49
lines changed

33 files changed

+773
-49
lines changed

.github/workflows/rust.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ jobs:
3737
- name: Check
3838
run: cargo check --verbose
3939

40+
- name: Add fmt
41+
run: rustup component add rustfmt
42+
43+
- name: Check formatting
44+
run: cargo fmt -- --check
45+
4046
- name: Test
4147
run: cargo test
4248
continue-on-error: true
@@ -59,12 +65,6 @@ jobs:
5965
- name: Add clippy
6066
run: rustup component add clippy
6167

62-
- name: Add fmt
63-
run: rustup component add rustfmt
64-
65-
- name: Check formatting
66-
run: cargo fmt -- --check
67-
6868
- name: Clippy
6969
run:
7070
cargo clippy

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# 0.3.13
2+
3+
* Add default mint configuration options
4+
* `default_mint_url`
5+
* `default_mint_node_id`
6+
* Implement `request_to_mint`
7+
* Change bitcoin addresses and descriptor to p2wpkh
8+
* Suppress logging from crates we don't control
9+
110
# 0.3.12
211

312
* impl Default for BillParticipant and BillAnonParticipant

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ opt-level = 1 # Avoid surrealdb index out of bounds issue in dev build
2020
sha2 = { version = "0.10", default-features = false }
2121
borsh = "1.5"
2222
borsh-derive = "1.5"
23-
env_logger = { version = "0.11", default-features = false }
2423
log = { version = "0.4", features = ["serde"] }
2524
chrono = { version = "0.4", default-features = false, features = [
2625
"serde",

crates/bcr-ebill-api/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bcr-ebill-api"
3-
version = "0.3.12"
3+
version = "0.3.13"
44
edition = "2024"
55
license = "MIT"
66

@@ -31,6 +31,9 @@ bcr-ebill-transport = { path = "../bcr-ebill-transport" }
3131
tokio.workspace = true
3232
tokio_with_wasm.workspace = true
3333
secp256k1.workspace = true
34+
bcr-wdc-webapi = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "db63f77e092e529912baebd201f998479b8fefcb" }
35+
bcr-wdc-utils = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "db63f77e092e529912baebd201f998479b8fefcb" }
36+
cashu = { version = "0.9", default-features = false }
3437

3538
[target.'cfg(target_arch = "wasm32")'.dependencies]
3639
reqwest = { workspace = true, features = ["json"] }
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use async_trait::async_trait;
2+
use bcr_ebill_core::{
3+
PostalAddress, ServiceTraitBounds,
4+
bill::BitcreditBill,
5+
contact::{BillAnonParticipant, BillIdentParticipant, BillParticipant, ContactType},
6+
util::BcrKeys,
7+
};
8+
use bcr_wdc_webapi::quotes::{BillInfo, EnquireReply, EnquireRequest};
9+
use cashu::nut01 as cdk01;
10+
use thiserror::Error;
11+
12+
/// Generic result type
13+
pub type Result<T> = std::result::Result<T, super::Error>;
14+
15+
/// Generic error type
16+
#[derive(Debug, Error)]
17+
pub enum Error {
18+
/// all errors originating from interacting with the web api
19+
#[error("External Mint Web API error: {0}")]
20+
Api(#[from] reqwest::Error),
21+
/// all errors originating from parsing public keys
22+
#[error("External Mint Public Key Error")]
23+
PubKey,
24+
/// all errors originating from creating signatures
25+
#[error("External Mint Signature Error")]
26+
Signature,
27+
/// all errors originating invalid dates
28+
#[error("External Mint Invalid Date Error")]
29+
InvalidDate,
30+
}
31+
32+
#[cfg(test)]
33+
use mockall::automock;
34+
35+
use crate::util;
36+
37+
#[cfg_attr(test, automock)]
38+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
39+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
40+
pub trait MintClientApi: ServiceTraitBounds {
41+
async fn enquire_mint_quote(
42+
&self,
43+
mint_url: &str,
44+
requester_keys: &BcrKeys,
45+
bill: &BitcreditBill,
46+
endorsees: &[BillParticipant],
47+
) -> Result<String>;
48+
}
49+
50+
#[derive(Debug, Clone, Default)]
51+
pub struct MintClient {
52+
cl: reqwest::Client,
53+
}
54+
55+
impl ServiceTraitBounds for MintClient {}
56+
57+
#[cfg(test)]
58+
impl ServiceTraitBounds for MockMintClientApi {}
59+
60+
impl MintClient {
61+
pub fn new() -> Self {
62+
Self {
63+
cl: reqwest::Client::new(),
64+
}
65+
}
66+
}
67+
68+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
69+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
70+
impl MintClientApi for MintClient {
71+
async fn enquire_mint_quote(
72+
&self,
73+
mint_url: &str,
74+
requester_keys: &BcrKeys,
75+
bill: &BitcreditBill,
76+
endorsees: &[BillParticipant],
77+
) -> Result<String> {
78+
let bill_info = BillInfo {
79+
id: bill.id.clone(),
80+
drawee: map_bill_ident_participant(bill.drawee.to_owned()),
81+
drawer: map_bill_ident_participant(bill.drawer.to_owned()),
82+
payee: map_bill_participant(bill.payee.to_owned()),
83+
endorsees: endorsees
84+
.iter()
85+
.map(|e| map_bill_participant(e.to_owned()))
86+
.collect(),
87+
sum: bill.sum,
88+
maturity_date: util::date::date_string_to_rfc3339(&bill.maturity_date)
89+
.map_err(|_| Error::InvalidDate)?,
90+
};
91+
let public_key = cdk01::PublicKey::from_hex(requester_keys.get_public_key())
92+
.map_err(|_| Error::PubKey)?;
93+
94+
let signature = bcr_wdc_utils::keys::schnorr_sign_borsh_msg_with_key(
95+
&bill_info,
96+
&requester_keys.get_key_pair(),
97+
)
98+
.map_err(|_| Error::Signature)?;
99+
100+
let payload: EnquireRequest = EnquireRequest {
101+
content: bill_info,
102+
signature,
103+
public_key,
104+
};
105+
let url = format!("{}/v1/mint/credit/quote", mint_url);
106+
let res = self
107+
.cl
108+
.post(&url)
109+
.json(&payload)
110+
.send()
111+
.await
112+
.map_err(Error::from)?;
113+
let reply: EnquireReply = res.json().await.map_err(Error::from)?;
114+
Ok(reply.id.to_string())
115+
}
116+
}
117+
118+
// These are needed for now, because we use different `bcr-ebill-core` versions in wildcat and here
119+
// this should be remediated, once we're stable on both sides
120+
121+
fn map_bill_participant(part: BillParticipant) -> bcr_wdc_webapi::bill::BillParticipant {
122+
match part {
123+
BillParticipant::Ident(data) => {
124+
bcr_wdc_webapi::bill::BillParticipant::Ident(map_bill_ident_participant(data))
125+
}
126+
BillParticipant::Anon(data) => {
127+
bcr_wdc_webapi::bill::BillParticipant::Anon(map_bill_anon_participant(data))
128+
}
129+
}
130+
}
131+
132+
fn map_bill_anon_participant(
133+
ident: BillAnonParticipant,
134+
) -> bcr_wdc_webapi::bill::BillAnonParticipant {
135+
bcr_wdc_webapi::bill::BillAnonParticipant {
136+
node_id: ident.node_id,
137+
email: ident.email,
138+
nostr_relays: ident.nostr_relays,
139+
}
140+
}
141+
142+
fn map_bill_ident_participant(
143+
ident: BillIdentParticipant,
144+
) -> bcr_wdc_webapi::bill::BillIdentParticipant {
145+
bcr_wdc_webapi::bill::BillIdentParticipant {
146+
t: map_contact_type(ident.t),
147+
node_id: ident.node_id,
148+
name: ident.name,
149+
postal_address: map_postal_address(ident.postal_address),
150+
email: ident.email,
151+
nostr_relays: ident.nostr_relays,
152+
}
153+
}
154+
155+
fn map_contact_type(ct: ContactType) -> bcr_wdc_webapi::contact::ContactType {
156+
match ct {
157+
ContactType::Person => bcr_wdc_webapi::contact::ContactType::Person,
158+
ContactType::Company => bcr_wdc_webapi::contact::ContactType::Company,
159+
ContactType::Anon => bcr_wdc_webapi::contact::ContactType::Anon,
160+
}
161+
}
162+
163+
fn map_postal_address(pa: PostalAddress) -> bcr_wdc_webapi::identity::PostalAddress {
164+
bcr_wdc_webapi::identity::PostalAddress {
165+
country: pa.country,
166+
city: pa.city,
167+
zip: pa.zip,
168+
address: pa.address,
169+
}
170+
}

crates/bcr-ebill-api/src/external/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod bitcoin;
2+
pub mod mint;
23
pub mod time;
34

45
use thiserror::Error;
@@ -13,4 +14,8 @@ pub enum Error {
1314
/// all errors originating from the external bitcoin API
1415
#[error("External Bitcoin API error: {0}")]
1516
ExternalBitcoinApi(#[from] bitcoin::Error),
17+
18+
/// all errors originating from the external mint API
19+
#[error("External Mint API error: {0}")]
20+
ExternalMintApi(#[from] mint::Error),
1621
}

crates/bcr-ebill-api/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct Config {
2727
pub db_config: SurrealDbConfig,
2828
pub data_dir: String,
2929
pub nostr_config: NostrConfig,
30+
pub mint_config: MintConfig,
3031
}
3132

3233
static CONFIG: OnceLock<Config> = OnceLock::new();
@@ -52,6 +53,27 @@ pub struct NostrConfig {
5253
pub relays: Vec<String>,
5354
}
5455

56+
/// Mint configuration
57+
#[derive(Debug, Clone, Default)]
58+
pub struct MintConfig {
59+
/// URL of the default mint
60+
pub default_mint_url: String,
61+
/// Node Id of the default mint
62+
pub default_mint_node_id: String,
63+
}
64+
65+
impl MintConfig {
66+
pub fn new(default_mint_url: String, default_mint_node_id: String) -> Result<Self> {
67+
util::crypto::validate_pub_key(&default_mint_node_id)?;
68+
reqwest::Url::parse(&default_mint_url)
69+
.map_err(|e| anyhow!("Invalid Default Mint URL: {e}"))?;
70+
Ok(Self {
71+
default_mint_url,
72+
default_mint_node_id,
73+
})
74+
}
75+
}
76+
5577
pub fn init(conf: Config) -> Result<()> {
5678
CONFIG
5779
.set(conf)

crates/bcr-ebill-api/src/persistence/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ use bcr_ebill_persistence::{
99
bill::{BillChainStoreApi, BillStoreApi},
1010
company::{CompanyChainStoreApi, CompanyStoreApi},
1111
db::{
12-
nostr_contact_store::SurrealNostrContactStore,
12+
mint::SurrealMintStore, nostr_contact_store::SurrealNostrContactStore,
1313
nostr_send_queue::SurrealNostrEventQueueStore, surreal::SurrealWrapper,
1414
},
1515
file_upload::FileUploadStoreApi,
1616
identity::{IdentityChainStoreApi, IdentityStoreApi},
17+
mint::MintStoreApi,
1718
nostr::{NostrContactStoreApi, NostrQueuedMessageStoreApi},
1819
};
1920
use log::error;
@@ -47,6 +48,7 @@ pub struct DbContext {
4748
pub backup_store: Arc<dyn BackupStoreApi>,
4849
pub queued_message_store: Arc<dyn NostrQueuedMessageStoreApi>,
4950
pub nostr_contact_store: Arc<dyn NostrContactStoreApi>,
51+
pub mint_store: Arc<dyn MintStoreApi>,
5052
}
5153

5254
/// Creates a new instance of the DbContext with the given SurrealDB configuration.
@@ -106,6 +108,7 @@ pub async fn get_db_context(
106108
let backup_store = Arc::new(SurrealBackupStore::new(db.clone()));
107109
let queued_message_store = Arc::new(SurrealNostrEventQueueStore::new(surreal_wrapper.clone()));
108110
let nostr_contact_store = Arc::new(SurrealNostrContactStore::new(surreal_wrapper.clone()));
111+
let mint_store = Arc::new(SurrealMintStore::new(surreal_wrapper.clone()));
109112

110113
Ok(DbContext {
111114
contact_store,
@@ -121,5 +124,6 @@ pub async fn get_db_context(
121124
backup_store,
122125
queued_message_store,
123126
nostr_contact_store,
127+
mint_store,
124128
})
125129
}

crates/bcr-ebill-api/src/service/bill_service/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ pub trait BillServiceApi: ServiceTraitBounds {
109109
timestamp: u64,
110110
) -> Result<BillBlockchain>;
111111

112+
/// request to mint a bill
113+
async fn request_to_mint(
114+
&self,
115+
bill_id: &str,
116+
mint_node_id: &str,
117+
signer_public_data: &BillParticipant,
118+
signer_keys: &BcrKeys,
119+
timestamp: u64,
120+
) -> Result<()>;
121+
112122
/// Check payment status of bills that are requested to pay and not expired and not paid yet, updating their
113123
/// paid status if they were paid
114124
async fn check_bills_payment(&self) -> Result<()>;

0 commit comments

Comments
 (0)