mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-24 08:05:02 +01:00
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -106,6 +106,8 @@ jobs:
|
||||
--bin cdk-mintd --no-default-features --features swagger,
|
||||
--bin cdk-mintd --no-default-features --features redis,
|
||||
--bin cdk-mintd --no-default-features --features "redis swagger",
|
||||
--bin cdk-mintd --no-default-features --features management-rpc,
|
||||
--bin cdk-mint-cli,
|
||||
]
|
||||
steps:
|
||||
- name: checkout
|
||||
@@ -213,6 +215,7 @@ jobs:
|
||||
-p cdk-phoenixd,
|
||||
-p cdk-fake-wallet,
|
||||
-p cdk-cln,
|
||||
-p cdk-mint-rpc,
|
||||
]
|
||||
steps:
|
||||
- name: checkout
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
[language-server.rust-analyzer.config]
|
||||
cargo = { features = ["wallet", "mint", "swagger", "redis"] }
|
||||
|
||||
@@ -26,9 +26,11 @@ The project is split up into several crates in the `crates/` directory:
|
||||
* [**cdk-lnbits**](./crates/cdk-lnbits/): [LNbits](https://lnbits.com/) Lightning backend for mint.
|
||||
* [**cdk-phoenixd**](./crates/cdk-phoenixd/): Phoenixd Lightning backend for mint.
|
||||
* [**cdk-fake-wallet**](./crates/cdk-fake-wallet/): Fake Lightning backend for mint. To be used only for testing, quotes are automatically filled.
|
||||
* [**cdk-mint-rpc**](./crates/cdk-mint-rpc/): Mint management gRPC server and cli.
|
||||
* Binaries:
|
||||
* [**cdk-cli**](./crates/cdk-cli/): Cashu wallet CLI.
|
||||
* [**cdk-mintd**](./crates/cdk-mintd/): Cashu Mint Binary.
|
||||
* [**cdk-mint-cli**](./crates/cdk-mint-rpc/): Cashu Mint managemtn gRCP client cli.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
@@ -240,4 +240,16 @@ impl Settings {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Remove [`MintMethodSettings`] for unit method pair
|
||||
pub fn remove_settings(
|
||||
&mut self,
|
||||
unit: &CurrencyUnit,
|
||||
method: &PaymentMethod,
|
||||
) -> Option<MintMethodSettings> {
|
||||
self.methods
|
||||
.iter()
|
||||
.position(|settings| &settings.method == method && &settings.unit == unit)
|
||||
.map(|index| self.methods.remove(index))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +399,18 @@ impl Settings {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Remove [`MeltMethodSettings`] for unit method pair
|
||||
pub fn remove_settings(
|
||||
&mut self,
|
||||
unit: &CurrencyUnit,
|
||||
method: &PaymentMethod,
|
||||
) -> Option<MeltMethodSettings> {
|
||||
self.methods
|
||||
.iter()
|
||||
.position(|settings| settings.method.eq(method) && settings.unit.eq(unit))
|
||||
.map(|index| self.methods.remove(index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Melt Settings
|
||||
|
||||
@@ -26,6 +26,12 @@ impl MintVersion {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MintVersion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}/{}", self.name, self.version)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for MintVersion {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
@@ -28,7 +28,7 @@ futures = { version = "0.3.28", default-features = false }
|
||||
moka = { version = "0.11.1", features = ["future"] }
|
||||
serde_json = "1"
|
||||
paste = "1.0.15"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
sha2 = "0.10.8"
|
||||
redis = { version = "0.23.3", features = [
|
||||
|
||||
@@ -17,7 +17,7 @@ bip39 = "2.0"
|
||||
cdk = { path = "../cdk", version = "0.6.0", default-features = false, features = ["wallet"]}
|
||||
cdk-redb = { path = "../cdk-redb", version = "0.6.0", default-features = false, features = ["wallet"] }
|
||||
cdk-sqlite = { path = "../cdk-sqlite", version = "0.6.0", default-features = false, features = ["wallet"] }
|
||||
clap = { version = "4.4.8", features = ["derive", "env", "default"] }
|
||||
clap = { version = "~4.0.32", features = ["derive"] }
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", default-features = false }
|
||||
|
||||
@@ -6,6 +6,7 @@ use bip39::Mnemonic;
|
||||
use cdk::cdk_database::{self, MintDatabase};
|
||||
use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits};
|
||||
use cdk::nuts::{CurrencyUnit, PaymentMethod};
|
||||
use cdk::types::QuoteTTL;
|
||||
use cdk_fake_wallet::FakeWallet;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
@@ -37,7 +38,8 @@ where
|
||||
|
||||
let mut mint_builder = MintBuilder::new();
|
||||
|
||||
mint_builder = mint_builder.with_localstore(Arc::new(database));
|
||||
let localstore = Arc::new(database);
|
||||
mint_builder = mint_builder.with_localstore(localstore.clone());
|
||||
|
||||
mint_builder = mint_builder.add_ln_backend(
|
||||
CurrencyUnit::Sat,
|
||||
@@ -65,9 +67,14 @@ where
|
||||
mint_builder = mint_builder
|
||||
.with_name("fake test mint".to_string())
|
||||
.with_description("fake test mint".to_string())
|
||||
.with_quote_ttl(10000, 10000)
|
||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||
|
||||
localstore
|
||||
.set_mint_info(mint_builder.mint_info.clone())
|
||||
.await?;
|
||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||
localstore.set_quote_ttl(quote_ttl).await?;
|
||||
|
||||
let mint = mint_builder.build().await?;
|
||||
|
||||
start_mint(addr, port, mint).await?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use async_trait::async_trait;
|
||||
use bip39::Mnemonic;
|
||||
use cdk::amount::SplitTarget;
|
||||
use cdk::cdk_database::mint_memory::MintMemoryDatabase;
|
||||
use cdk::cdk_database::WalletMemoryDatabase;
|
||||
use cdk::cdk_database::{MintDatabase, WalletMemoryDatabase};
|
||||
use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits};
|
||||
use cdk::nuts::nut00::ProofsMethods;
|
||||
use cdk::nuts::{
|
||||
@@ -16,6 +16,7 @@ use cdk::nuts::{
|
||||
MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, PaymentMethod,
|
||||
RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
|
||||
};
|
||||
use cdk::types::QuoteTTL;
|
||||
use cdk::util::unix_time;
|
||||
use cdk::wallet::client::MintConnector;
|
||||
use cdk::wallet::Wallet;
|
||||
@@ -146,7 +147,8 @@ pub async fn create_and_start_test_mint() -> anyhow::Result<Arc<Mint>> {
|
||||
|
||||
let database = MintMemoryDatabase::default();
|
||||
|
||||
mint_builder = mint_builder.with_localstore(Arc::new(database));
|
||||
let localstore = Arc::new(database);
|
||||
mint_builder = mint_builder.with_localstore(localstore.clone());
|
||||
|
||||
let fee_reserve = FeeReserve {
|
||||
min_fee_reserve: 1.into(),
|
||||
@@ -172,9 +174,14 @@ pub async fn create_and_start_test_mint() -> anyhow::Result<Arc<Mint>> {
|
||||
mint_builder = mint_builder
|
||||
.with_name("pure test mint".to_string())
|
||||
.with_description("pure test mint".to_string())
|
||||
.with_quote_ttl(10000, 10000)
|
||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||
|
||||
localstore
|
||||
.set_mint_info(mint_builder.mint_info.clone())
|
||||
.await?;
|
||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||
localstore.set_quote_ttl(quote_ttl).await?;
|
||||
|
||||
let mint = mint_builder.build().await?;
|
||||
|
||||
let mint_arc = Arc::new(mint);
|
||||
|
||||
@@ -8,6 +8,7 @@ use cdk::cdk_database::{self, MintDatabase};
|
||||
use cdk::cdk_lightning::{self, MintLightning};
|
||||
use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits};
|
||||
use cdk::nuts::{CurrencyUnit, PaymentMethod};
|
||||
use cdk::types::QuoteTTL;
|
||||
use cdk_cln::Cln as CdkCln;
|
||||
use cdk_lnd::Lnd as CdkLnd;
|
||||
use ln_regtest_rs::bitcoin_client::BitcoinClient;
|
||||
@@ -155,8 +156,8 @@ where
|
||||
L: MintLightning<Err = cdk_lightning::Error> + Send + Sync + 'static,
|
||||
{
|
||||
let mut mint_builder = MintBuilder::new();
|
||||
|
||||
mint_builder = mint_builder.with_localstore(Arc::new(database));
|
||||
let localstore = Arc::new(database);
|
||||
mint_builder = mint_builder.with_localstore(localstore.clone());
|
||||
|
||||
mint_builder = mint_builder.add_ln_backend(
|
||||
CurrencyUnit::Sat,
|
||||
@@ -170,11 +171,16 @@ where
|
||||
mint_builder = mint_builder
|
||||
.with_name("regtest mint".to_string())
|
||||
.with_description("regtest mint".to_string())
|
||||
.with_quote_ttl(10000, 10000)
|
||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||
|
||||
let mint = mint_builder.build().await?;
|
||||
|
||||
localstore
|
||||
.set_mint_info(mint_builder.mint_info.clone())
|
||||
.await?;
|
||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||
localstore.set_quote_ttl(quote_ttl).await?;
|
||||
|
||||
start_mint(addr, port, mint).await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -17,6 +17,7 @@ use cdk::nuts::{
|
||||
PreMintSecrets, ProofState, Proofs, SecretKey, SpendingConditions, State, SwapRequest,
|
||||
};
|
||||
use cdk::subscription::{IndexableParams, Params};
|
||||
use cdk::types::QuoteTTL;
|
||||
use cdk::util::unix_time;
|
||||
use cdk::Mint;
|
||||
use cdk_fake_wallet::FakeWallet;
|
||||
@@ -465,11 +466,16 @@ async fn test_correct_keyset() -> Result<()> {
|
||||
mint_builder = mint_builder
|
||||
.with_name("regtest mint".to_string())
|
||||
.with_description("regtest mint".to_string())
|
||||
.with_quote_ttl(10000, 1000)
|
||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||
|
||||
let mint = mint_builder.build().await?;
|
||||
|
||||
localstore
|
||||
.set_mint_info(mint_builder.mint_info.clone())
|
||||
.await?;
|
||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||
localstore.set_quote_ttl(quote_ttl).await?;
|
||||
|
||||
mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?;
|
||||
mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?;
|
||||
|
||||
|
||||
41
crates/cdk-mint-rpc/Cargo.toml
Normal file
41
crates/cdk-mint-rpc/Cargo.toml
Normal file
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "cdk-mint-rpc"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
authors = ["CDK Developers"]
|
||||
description = "CDK mintd mint managment RPC client and server"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/cashubtc/cdk"
|
||||
repository = "https://github.com/cashubtc/cdk.git"
|
||||
rust-version = "1.63.0" # MSRV
|
||||
|
||||
[[bin]]
|
||||
name = "cdk-mint-cli"
|
||||
path = "src/bin/mint_rpc_cli.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
cdk = { path = "../cdk", version = "0.6.0", default-features = false, features = [
|
||||
"mint",
|
||||
] }
|
||||
clap = { version = "~4.0.32", features = ["derive"] }
|
||||
tonic = { version = "0.9", features = [
|
||||
"channel",
|
||||
"tls",
|
||||
"tls-webpki-roots",
|
||||
] }
|
||||
tracing = { version = "0.1", default-features = false, features = [
|
||||
"attributes",
|
||||
"log",
|
||||
] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tokio = { version = "1", default-features = false }
|
||||
serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
prost = "0.11.0"
|
||||
home = "0.5.5"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.9"
|
||||
93
crates/cdk-mint-rpc/README.md
Normal file
93
crates/cdk-mint-rpc/README.md
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
# Cashu Mint Management RPC
|
||||
|
||||
This crate is a grpc client and server to control and manage a cdk mint. This crate exposes a server complnate that can be imported as library compontant, see its usage in `cdk-mintd`. The client can be used as a cli by running `cargo r --bin cdk-mint-cli`.
|
||||
|
||||
The server can be run with or without certificate authentication. For running with authentication follow the below steps to create certificates.
|
||||
|
||||
|
||||
# gRPC TLS Certificate Generation Guide
|
||||
|
||||
This guide explains how to generate the necessary TLS certificates for securing gRPC communication between client and server.
|
||||
|
||||
## Overview
|
||||
|
||||
The script generates the following certificates and keys:
|
||||
- Certificate Authority (CA) certificate and key
|
||||
- Server certificate and key
|
||||
- Client certificate and key
|
||||
|
||||
All certificates are generated in PEM format, which is commonly used in Unix/Linux systems.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenSSL installed on your system
|
||||
- Bash shell environment
|
||||
|
||||
## Generated Files
|
||||
|
||||
The script will create the following files:
|
||||
- `ca.key` - Certificate Authority private key
|
||||
- `ca.pem` - Certificate Authority certificate
|
||||
- `server.key` - Server private key
|
||||
- `server.pem` - Server certificate
|
||||
- `client.key` - Client private key
|
||||
- `client.pem` - Client certificate
|
||||
|
||||
## Usage
|
||||
|
||||
1. Save the script as `generate_certs.sh`
|
||||
2. Make it executable:
|
||||
```bash
|
||||
chmod +x generate_certs.sh
|
||||
```
|
||||
3. Run the script:
|
||||
```bash
|
||||
./generate_certs.sh
|
||||
```
|
||||
|
||||
## Certificate Details
|
||||
|
||||
### Certificate Authority (CA)
|
||||
- 4096-bit RSA key
|
||||
- Valid for 365 days
|
||||
- Used to sign both server and client certificates
|
||||
|
||||
### Server Certificate
|
||||
- 4096-bit RSA key
|
||||
- Valid for 365 days
|
||||
- Includes Subject Alternative Names (SAN):
|
||||
- DNS: localhost
|
||||
- DNS: my-server
|
||||
- IP: 127.0.0.1
|
||||
|
||||
### Client Certificate
|
||||
- 4096-bit RSA key
|
||||
- Valid for 365 days
|
||||
- Used for client authentication
|
||||
|
||||
|
||||
## Verification
|
||||
|
||||
The script includes verification steps to ensure the certificates are properly generated:
|
||||
```bash
|
||||
# Verify server certificate
|
||||
openssl verify -CAfile ca.pem server.pem
|
||||
|
||||
# Verify client certificate
|
||||
openssl verify -CAfile ca.pem client.pem
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. Keep private keys (*.key files) secure and never share them
|
||||
2. The CA certificate (ca.pem) needs to be distributed to both client and server
|
||||
3. Server needs:
|
||||
- server.key
|
||||
- server.pem
|
||||
- ca.pem
|
||||
4. Client needs:
|
||||
- client.key
|
||||
- client.pem
|
||||
- ca.pem
|
||||
|
||||
5
crates/cdk-mint-rpc/build.rs
Normal file
5
crates/cdk-mint-rpc/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("cargo:rerun-if-changed=src/proto/cdk-mint-rpc.proto");
|
||||
tonic_build::compile_protos("src/proto/cdk-mint-rpc.proto")?;
|
||||
Ok(())
|
||||
}
|
||||
47
crates/cdk-mint-rpc/generate_certs.sh
Executable file
47
crates/cdk-mint-rpc/generate_certs.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
# Generate private key for Certificate Authority (CA)
|
||||
openssl genrsa -out ca.key 4096
|
||||
|
||||
# Generate CA certificate
|
||||
openssl req -new -x509 -days 365 -key ca.key -out ca.pem -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=MyCA"
|
||||
|
||||
# Generate private key for Server
|
||||
openssl genrsa -out server.key 4096
|
||||
|
||||
# Generate Certificate Signing Request (CSR) for Server
|
||||
openssl req -new -key server.key -out server.csr -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=localhost"
|
||||
|
||||
# Generate Server certificate
|
||||
openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -extfile <(printf "subjectAltName=DNS:localhost,DNS:my-server,IP:127.0.0.1")
|
||||
|
||||
# Generate private key for Client
|
||||
openssl genrsa -out client.key 4096
|
||||
|
||||
# Generate CSR for Client
|
||||
openssl req -new -key client.key -out client.csr -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=client"
|
||||
|
||||
# Generate Client certificate
|
||||
openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem
|
||||
|
||||
# Verify the certificates
|
||||
echo "Verifying Server Certificate:"
|
||||
openssl verify -CAfile ca.pem server.pem
|
||||
|
||||
echo "Verifying Client Certificate:"
|
||||
openssl verify -CAfile ca.pem client.pem
|
||||
|
||||
# Clean up CSR files (optional)
|
||||
rm server.csr client.csr
|
||||
|
||||
# Display certificate information
|
||||
echo "Server Certificate Info:"
|
||||
openssl x509 -in server.pem -text -noout | grep "Subject:\|Issuer:\|DNS:\|IP Address:"
|
||||
|
||||
echo "Client Certificate Info:"
|
||||
openssl x509 -in client.pem -text -noout | grep "Subject:\|Issuer:"
|
||||
|
||||
# Final files you'll need:
|
||||
# - ca.pem (Certificate Authority certificate)
|
||||
# - server.key (Server private key)
|
||||
# - server.pem (Server certificate)
|
||||
# - client.key (Client private key)
|
||||
# - client.pem (Client certificate)
|
||||
196
crates/cdk-mint-rpc/src/bin/mint_rpc_cli.rs
Normal file
196
crates/cdk-mint-rpc/src/bin/mint_rpc_cli.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use cdk_mint_rpc::cdk_mint_client::CdkMintClient;
|
||||
use cdk_mint_rpc::mint_rpc_cli::subcommands;
|
||||
use cdk_mint_rpc::GetInfoRequest;
|
||||
use clap::{Parser, Subcommand};
|
||||
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
||||
use tonic::Request;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
const DEFAULT_WORK_DIR: &str = ".cdk-mint-rpc-cli";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// Address of RPC server
|
||||
#[arg(short, long, default_value = "http://127.0.0.1:8086")]
|
||||
addr: String,
|
||||
|
||||
/// Logging level
|
||||
#[arg(short, long, default_value = "debug")]
|
||||
log_level: Level,
|
||||
|
||||
/// Path to working dir
|
||||
#[arg(short, long)]
|
||||
work_dir: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Get info
|
||||
GetInfo,
|
||||
/// Update motd
|
||||
UpdateMotd(subcommands::UpdateMotdCommand),
|
||||
/// Update short description
|
||||
UpdateShortDescription(subcommands::UpdateShortDescriptionCommand),
|
||||
/// Update long description
|
||||
UpdateLongDescription(subcommands::UpdateLongDescriptionCommand),
|
||||
/// Update name
|
||||
UpdateName(subcommands::UpdateNameCommand),
|
||||
/// Update icon url
|
||||
UpdateIconUrl(subcommands::UpdateIconUrlCommand),
|
||||
/// Add Url
|
||||
AddUrl(subcommands::AddUrlCommand),
|
||||
/// Remove Url
|
||||
RemoveUrl(subcommands::RemoveUrlCommand),
|
||||
/// Add contact
|
||||
AddContact(subcommands::AddContactCommand),
|
||||
/// Remove contact
|
||||
RemoveContact(subcommands::RemoveContactCommand),
|
||||
/// Update nut04
|
||||
UpdateNut04(subcommands::UpdateNut04Command),
|
||||
/// Update nut05
|
||||
UpdateNut05(subcommands::UpdateNut05Command),
|
||||
/// Update quote ttl
|
||||
UpdateQuoteTtl(subcommands::UpdateQuoteTtlCommand),
|
||||
/// Update Nut04 quote
|
||||
UpdateNut04QuoteState(subcommands::UpdateNut04QuoteCommand),
|
||||
/// Rotate next keyset
|
||||
RotateNextKeyset(subcommands::RotateNextKeysetCommand),
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args: Cli = Cli::parse();
|
||||
let default_filter = args.log_level;
|
||||
|
||||
let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
|
||||
|
||||
let env_filter = EnvFilter::new(format!("{},{}", default_filter, sqlx_filter));
|
||||
|
||||
// Parse input
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let work_dir = match &args.work_dir {
|
||||
Some(work_dir) => work_dir.clone(),
|
||||
None => {
|
||||
let home_dir = home::home_dir().ok_or(anyhow!("Could not find home dir"))?;
|
||||
|
||||
home_dir.join(DEFAULT_WORK_DIR)
|
||||
}
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&work_dir)?;
|
||||
tracing::debug!("Using work dir: {}", work_dir.display());
|
||||
|
||||
let channel = if work_dir.join("tls").is_dir() {
|
||||
// TLS directory exists, configure TLS
|
||||
let server_root_ca_cert = std::fs::read_to_string(work_dir.join("tls/ca.pem")).unwrap();
|
||||
let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert);
|
||||
let client_cert = std::fs::read_to_string(work_dir.join("tls/client.pem"))?;
|
||||
let client_key = std::fs::read_to_string(work_dir.join("tls/client.key"))?;
|
||||
let client_identity = Identity::from_pem(client_cert, client_key);
|
||||
let tls = ClientTlsConfig::new()
|
||||
.ca_certificate(server_root_ca_cert)
|
||||
.identity(client_identity);
|
||||
|
||||
Channel::from_shared(cli.addr.to_string())?
|
||||
.tls_config(tls)?
|
||||
.connect()
|
||||
.await?
|
||||
} else {
|
||||
// No TLS directory, skip TLS configuration
|
||||
Channel::from_shared(cli.addr.to_string())?
|
||||
.connect()
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut client = CdkMintClient::new(channel);
|
||||
|
||||
match cli.command {
|
||||
Commands::GetInfo => {
|
||||
let response = client.get_info(Request::new(GetInfoRequest {})).await?;
|
||||
let info = response.into_inner();
|
||||
println!(
|
||||
"name: {}",
|
||||
info.name.unwrap_or("None".to_string())
|
||||
);
|
||||
println!(
|
||||
"version: {}",
|
||||
info.version.unwrap_or("None".to_string())
|
||||
);
|
||||
println!(
|
||||
"description: {}",
|
||||
info.description.unwrap_or("None".to_string())
|
||||
);
|
||||
println!(
|
||||
"long description: {}",
|
||||
info.long_description.unwrap_or("None".to_string())
|
||||
);
|
||||
println!("motd: {}", info.motd.unwrap_or("None".to_string()));
|
||||
println!("icon_url: {}", info.icon_url.unwrap_or("None".to_string()));
|
||||
|
||||
for url in info.urls {
|
||||
println!("mint_url: {}", url);
|
||||
}
|
||||
|
||||
for contact in info.contact {
|
||||
println!("method: {}, info: {}", contact.method, contact.info);
|
||||
}
|
||||
println!("total issued: {} sat", info.total_issued);
|
||||
println!("total redeemed: {} sat", info.total_redeemed);
|
||||
}
|
||||
Commands::UpdateMotd(sub_command_args) => {
|
||||
subcommands::update_motd(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateShortDescription(sub_command_args) => {
|
||||
subcommands::update_short_description(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateLongDescription(sub_command_args) => {
|
||||
subcommands::update_long_description(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateName(sub_command_args) => {
|
||||
subcommands::update_name(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateIconUrl(sub_command_args) => {
|
||||
subcommands::update_icon_url(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::AddUrl(sub_command_args) => {
|
||||
subcommands::add_url(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::RemoveUrl(sub_command_args) => {
|
||||
subcommands::remove_url(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::AddContact(sub_command_args) => {
|
||||
subcommands::add_contact(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::RemoveContact(sub_command_args) => {
|
||||
subcommands::remove_contact(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateNut04(sub_command_args) => {
|
||||
subcommands::update_nut04(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateNut05(sub_command_args) => {
|
||||
subcommands::update_nut05(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateQuoteTtl(sub_command_args) => {
|
||||
subcommands::update_quote_ttl(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::UpdateNut04QuoteState(sub_command_args) => {
|
||||
subcommands::update_nut04_quote_state(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
Commands::RotateNextKeyset(sub_command_args) => {
|
||||
subcommands::rotate_next_keyset(&mut client, &sub_command_args).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
27
crates/cdk-mint-rpc/src/client.rs
Normal file
27
crates/cdk-mint-rpc/src/client.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::cdk_mint_rpc::cdk_mint_client::CdkMintClient;
|
||||
use crate::cdk_mint_rpc::cdk_mint_server::CdkMint;
|
||||
|
||||
/// Error
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Transport error
|
||||
#[error(transparent)]
|
||||
Transport(#[from] tonic::transport::Error),
|
||||
}
|
||||
|
||||
pub struct MintRPCClient {
|
||||
inner: CdkMintClient<tonic::transport::Channel>,
|
||||
}
|
||||
|
||||
impl MintRPCClient {
|
||||
pub async fn new(url: String) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
inner: CdkMintClient::connect(url).await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl CdkMint for MintRPCClient {}
|
||||
5
crates/cdk-mint-rpc/src/lib.rs
Normal file
5
crates/cdk-mint-rpc/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod proto;
|
||||
|
||||
pub mod mint_rpc_cli;
|
||||
|
||||
pub use proto::*;
|
||||
1
crates/cdk-mint-rpc/src/mint_rpc_cli/mod.rs
Normal file
1
crates/cdk-mint-rpc/src/mint_rpc_cli/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod subcommands;
|
||||
25
crates/cdk-mint-rpc/src/mint_rpc_cli/subcommands/mod.rs
Normal file
25
crates/cdk-mint-rpc/src/mint_rpc_cli/subcommands/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
mod rotate_next_keyset;
|
||||
mod update_contact;
|
||||
mod update_icon_url;
|
||||
mod update_long_description;
|
||||
mod update_motd;
|
||||
mod update_name;
|
||||
mod update_nut04;
|
||||
mod update_nut04_quote;
|
||||
mod update_nut05;
|
||||
mod update_short_description;
|
||||
mod update_ttl;
|
||||
mod update_urls;
|
||||
|
||||
pub use rotate_next_keyset::{rotate_next_keyset, RotateNextKeysetCommand};
|
||||
pub use update_contact::{add_contact, remove_contact, AddContactCommand, RemoveContactCommand};
|
||||
pub use update_icon_url::{update_icon_url, UpdateIconUrlCommand};
|
||||
pub use update_long_description::{update_long_description, UpdateLongDescriptionCommand};
|
||||
pub use update_motd::{update_motd, UpdateMotdCommand};
|
||||
pub use update_name::{update_name, UpdateNameCommand};
|
||||
pub use update_nut04::{update_nut04, UpdateNut04Command};
|
||||
pub use update_nut04_quote::{update_nut04_quote_state, UpdateNut04QuoteCommand};
|
||||
pub use update_nut05::{update_nut05, UpdateNut05Command};
|
||||
pub use update_short_description::{update_short_description, UpdateShortDescriptionCommand};
|
||||
pub use update_ttl::{update_quote_ttl, UpdateQuoteTtlCommand};
|
||||
pub use update_urls::{add_url, remove_url, AddUrlCommand, RemoveUrlCommand};
|
||||
@@ -0,0 +1,40 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::RotateNextKeysetRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct RotateNextKeysetCommand {
|
||||
#[arg(short, long)]
|
||||
#[arg(default_value = "sat")]
|
||||
unit: String,
|
||||
#[arg(short, long)]
|
||||
max_order: Option<u8>,
|
||||
#[arg(short, long)]
|
||||
input_fee_ppk: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn rotate_next_keyset(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &RotateNextKeysetCommand,
|
||||
) -> Result<()> {
|
||||
let response = client
|
||||
.rotate_next_keyset(Request::new(RotateNextKeysetRequest {
|
||||
unit: sub_command_args.unit.clone(),
|
||||
max_order: sub_command_args.max_order.map(|m| m.into()),
|
||||
input_fee_ppk: sub_command_args.input_fee_ppk,
|
||||
}))
|
||||
.await?;
|
||||
|
||||
let response = response.into_inner();
|
||||
|
||||
println!(
|
||||
"Rotated to new keyset {} for unit {} with a max order of {} and fee of {}",
|
||||
response.id, response.unit, response.max_order, response.input_fee_ppk
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateContactRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct AddContactCommand {
|
||||
method: String,
|
||||
info: String,
|
||||
}
|
||||
|
||||
pub async fn add_contact(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &AddContactCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.add_contact(Request::new(UpdateContactRequest {
|
||||
method: sub_command_args.method.clone(),
|
||||
info: sub_command_args.info.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct RemoveContactCommand {
|
||||
method: String,
|
||||
info: String,
|
||||
}
|
||||
|
||||
pub async fn remove_contact(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &RemoveContactCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.remove_contact(Request::new(UpdateContactRequest {
|
||||
method: sub_command_args.method.clone(),
|
||||
info: sub_command_args.info.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateIconUrlRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateIconUrlCommand {
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub async fn update_icon_url(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateIconUrlCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_icon_url(Request::new(UpdateIconUrlRequest {
|
||||
icon_url: sub_command_args.name.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateDescriptionRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateLongDescriptionCommand {
|
||||
description: String,
|
||||
}
|
||||
|
||||
pub async fn update_long_description(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateLongDescriptionCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_long_description(Request::new(UpdateDescriptionRequest {
|
||||
description: sub_command_args.description.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateMotdRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateMotdCommand {
|
||||
motd: String,
|
||||
}
|
||||
|
||||
pub async fn update_motd(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateMotdCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_motd(Request::new(UpdateMotdRequest {
|
||||
motd: sub_command_args.motd.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateNameRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateNameCommand {
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub async fn update_name(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateNameCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_name(Request::new(UpdateNameRequest {
|
||||
name: sub_command_args.name.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateNut04Request;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateNut04Command {
|
||||
#[arg(short, long)]
|
||||
#[arg(default_value = "sat")]
|
||||
unit: String,
|
||||
#[arg(short, long)]
|
||||
#[arg(default_value = "bolt11")]
|
||||
method: String,
|
||||
#[arg(long)]
|
||||
min_amount: Option<u64>,
|
||||
#[arg(long)]
|
||||
max_amount: Option<u64>,
|
||||
#[arg(long)]
|
||||
disabled: Option<bool>,
|
||||
#[arg(long)]
|
||||
description: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn update_nut04(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateNut04Command,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_nut04(Request::new(UpdateNut04Request {
|
||||
method: sub_command_args.method.clone(),
|
||||
unit: sub_command_args.unit.clone(),
|
||||
disabled: sub_command_args.disabled,
|
||||
min: sub_command_args.min_amount,
|
||||
max: sub_command_args.max_amount,
|
||||
description: sub_command_args.description,
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateNut04QuoteRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateNut04QuoteCommand {
|
||||
quote_id: String,
|
||||
#[arg(default_value = "PAID")]
|
||||
state: String,
|
||||
}
|
||||
|
||||
pub async fn update_nut04_quote_state(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateNut04QuoteCommand,
|
||||
) -> Result<()> {
|
||||
let response = client
|
||||
.update_nut04_quote(Request::new(UpdateNut04QuoteRequest {
|
||||
quote_id: sub_command_args.quote_id.clone(),
|
||||
state: sub_command_args.state.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
let response = response.into_inner();
|
||||
|
||||
println!("Quote {} updated to {}", response.quote_id, response.state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateNut05Request;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateNut05Command {
|
||||
#[arg(short, long)]
|
||||
#[arg(default_value = "sat")]
|
||||
unit: String,
|
||||
#[arg(short, long)]
|
||||
#[arg(default_value = "bolt11")]
|
||||
method: String,
|
||||
#[arg(long)]
|
||||
min_amount: Option<u64>,
|
||||
#[arg(long)]
|
||||
max_amount: Option<u64>,
|
||||
#[arg(long)]
|
||||
disabled: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn update_nut05(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateNut05Command,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_nut05(Request::new(UpdateNut05Request {
|
||||
method: sub_command_args.method.clone(),
|
||||
unit: sub_command_args.unit.clone(),
|
||||
disabled: sub_command_args.disabled,
|
||||
min: sub_command_args.min_amount,
|
||||
max: sub_command_args.max_amount,
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateDescriptionRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateShortDescriptionCommand {
|
||||
description: String,
|
||||
}
|
||||
|
||||
pub async fn update_short_description(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateShortDescriptionCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_short_description(Request::new(UpdateDescriptionRequest {
|
||||
description: sub_command_args.description.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateQuoteTtlRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct UpdateQuoteTtlCommand {
|
||||
#[arg(long)]
|
||||
mint_ttl: Option<u64>,
|
||||
#[arg(long)]
|
||||
melt_ttl: Option<u64>,
|
||||
}
|
||||
pub async fn update_quote_ttl(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &UpdateQuoteTtlCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.update_quote_ttl(Request::new(UpdateQuoteTtlRequest {
|
||||
mint_ttl: sub_command_args.mint_ttl,
|
||||
melt_ttl: sub_command_args.melt_ttl,
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
use crate::cdk_mint_client::CdkMintClient;
|
||||
use crate::UpdateUrlRequest;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct AddUrlCommand {
|
||||
url: String,
|
||||
}
|
||||
|
||||
pub async fn add_url(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &AddUrlCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.add_url(Request::new(UpdateUrlRequest {
|
||||
url: sub_command_args.url.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct RemoveUrlCommand {
|
||||
url: String,
|
||||
}
|
||||
|
||||
pub async fn remove_url(
|
||||
client: &mut CdkMintClient<Channel>,
|
||||
sub_command_args: &RemoveUrlCommand,
|
||||
) -> Result<()> {
|
||||
let _response = client
|
||||
.remove_url(Request::new(UpdateUrlRequest {
|
||||
url: sub_command_args.url.clone(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
115
crates/cdk-mint-rpc/src/proto/cdk-mint-rpc.proto
Normal file
115
crates/cdk-mint-rpc/src/proto/cdk-mint-rpc.proto
Normal file
@@ -0,0 +1,115 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cdk_mint_rpc;
|
||||
|
||||
service CdkMint {
|
||||
rpc GetInfo(GetInfoRequest) returns (GetInfoResponse) {}
|
||||
rpc UpdateMotd(UpdateMotdRequest) returns (UpdateResponse) {}
|
||||
rpc UpdateShortDescription(UpdateDescriptionRequest) returns (UpdateResponse) {}
|
||||
rpc UpdateLongDescription(UpdateDescriptionRequest) returns (UpdateResponse) {}
|
||||
rpc UpdateIconUrl(UpdateIconUrlRequest) returns (UpdateResponse) {}
|
||||
rpc UpdateName(UpdateNameRequest) returns (UpdateResponse) {}
|
||||
rpc AddUrl(UpdateUrlRequest) returns (UpdateResponse) {}
|
||||
rpc RemoveUrl(UpdateUrlRequest) returns (UpdateResponse) {}
|
||||
rpc AddContact(UpdateContactRequest) returns (UpdateResponse) {}
|
||||
rpc RemoveContact(UpdateContactRequest) returns (UpdateResponse) {}
|
||||
rpc UpdateNut04(UpdateNut04Request) returns (UpdateResponse) {}
|
||||
rpc UpdateNut05(UpdateNut05Request) returns (UpdateResponse) {}
|
||||
rpc UpdateQuoteTtl(UpdateQuoteTtlRequest) returns (UpdateResponse) {}
|
||||
rpc UpdateNut04Quote(UpdateNut04QuoteRequest) returns (UpdateNut04QuoteRequest) {}
|
||||
rpc RotateNextKeyset(RotateNextKeysetRequest) returns (RotateNextKeysetResponse) {}
|
||||
}
|
||||
|
||||
message GetInfoRequest {
|
||||
}
|
||||
|
||||
message ContactInfo {
|
||||
string method = 1;
|
||||
string info = 2;
|
||||
}
|
||||
|
||||
message GetInfoResponse {
|
||||
optional string name = 1;
|
||||
optional string version = 2;
|
||||
optional string description = 3;
|
||||
optional string long_description = 4;
|
||||
repeated ContactInfo contact = 5;
|
||||
optional string motd = 6;
|
||||
optional string icon_url = 7;
|
||||
repeated string urls = 8;
|
||||
uint64 total_issued = 9;
|
||||
uint64 total_redeemed = 10;
|
||||
}
|
||||
|
||||
message UpdateResponse{
|
||||
}
|
||||
|
||||
message UpdateMotdRequest {
|
||||
string motd = 1;
|
||||
}
|
||||
|
||||
message UpdateDescriptionRequest {
|
||||
string description = 1;
|
||||
}
|
||||
|
||||
|
||||
message UpdateIconUrlRequest {
|
||||
string icon_url = 1;
|
||||
}
|
||||
|
||||
message UpdateNameRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
|
||||
message UpdateUrlRequest {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
message UpdateContactRequest {
|
||||
string method = 1;
|
||||
string info = 2;
|
||||
}
|
||||
|
||||
message UpdateNut04Request {
|
||||
string unit = 1;
|
||||
string method = 2;
|
||||
optional bool disabled = 3;
|
||||
optional uint64 min = 4;
|
||||
optional uint64 max = 5;
|
||||
optional bool description = 6;
|
||||
}
|
||||
|
||||
|
||||
message UpdateNut05Request {
|
||||
string unit = 1;
|
||||
string method = 2;
|
||||
optional bool disabled = 3;
|
||||
optional uint64 min = 4;
|
||||
optional uint64 max = 5;
|
||||
}
|
||||
|
||||
message UpdateQuoteTtlRequest {
|
||||
optional uint64 mint_ttl = 1;
|
||||
optional uint64 melt_ttl = 2;
|
||||
}
|
||||
|
||||
|
||||
message UpdateNut04QuoteRequest {
|
||||
string quote_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message RotateNextKeysetRequest {
|
||||
string unit = 1;
|
||||
optional uint32 max_order = 2;
|
||||
optional uint64 input_fee_ppk = 3;
|
||||
}
|
||||
|
||||
|
||||
message RotateNextKeysetResponse {
|
||||
string id = 1;
|
||||
string unit = 2;
|
||||
uint32 max_order = 3;
|
||||
uint64 input_fee_ppk = 4;
|
||||
}
|
||||
5
crates/cdk-mint-rpc/src/proto/mod.rs
Normal file
5
crates/cdk-mint-rpc/src/proto/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
tonic::include_proto!("cdk_mint_rpc");
|
||||
|
||||
mod server;
|
||||
|
||||
pub use server::MintRPCServer;
|
||||
586
crates/cdk-mint-rpc/src/proto/server.rs
Normal file
586
crates/cdk-mint-rpc/src/proto/server.rs
Normal file
@@ -0,0 +1,586 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cdk::mint::Mint;
|
||||
use cdk::nuts::nut04::MintMethodSettings;
|
||||
use cdk::nuts::nut05::MeltMethodSettings;
|
||||
use cdk::nuts::{CurrencyUnit, MintQuoteState, PaymentMethod};
|
||||
use cdk::types::QuoteTTL;
|
||||
use cdk::Amount;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::Notify;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::Duration;
|
||||
use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::cdk_mint_server::{CdkMint, CdkMintServer};
|
||||
use crate::{
|
||||
ContactInfo, GetInfoRequest, GetInfoResponse, RotateNextKeysetRequest,
|
||||
RotateNextKeysetResponse, UpdateContactRequest, UpdateDescriptionRequest, UpdateIconUrlRequest,
|
||||
UpdateMotdRequest, UpdateNameRequest, UpdateNut04QuoteRequest, UpdateNut04Request,
|
||||
UpdateNut05Request, UpdateQuoteTtlRequest, UpdateResponse, UpdateUrlRequest,
|
||||
};
|
||||
|
||||
/// Error
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Parse error
|
||||
#[error(transparent)]
|
||||
Parse(#[from] std::net::AddrParseError),
|
||||
/// Transport error
|
||||
#[error(transparent)]
|
||||
Transport(#[from] tonic::transport::Error),
|
||||
/// Io error
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
/// CDK Mint RPC Server
|
||||
#[derive(Clone)]
|
||||
pub struct MintRPCServer {
|
||||
socket_addr: SocketAddr,
|
||||
mint: Arc<Mint>,
|
||||
shutdown: Arc<Notify>,
|
||||
handle: Option<Arc<JoinHandle<Result<(), Error>>>>,
|
||||
}
|
||||
|
||||
impl MintRPCServer {
|
||||
pub fn new(addr: &str, port: u16, mint: Arc<Mint>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
socket_addr: format!("{addr}:{port}").parse()?,
|
||||
mint,
|
||||
shutdown: Arc::new(Notify::new()),
|
||||
handle: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn start(&mut self, tls_dir: Option<PathBuf>) -> Result<(), Error> {
|
||||
tracing::info!("Starting RPC server {}", self.socket_addr);
|
||||
|
||||
let server = match tls_dir {
|
||||
Some(tls_dir) => {
|
||||
tracing::info!("TLS configuration found, starting secure server");
|
||||
let cert = std::fs::read_to_string(tls_dir.join("server.pem"))?;
|
||||
let key = std::fs::read_to_string(tls_dir.join("server.key"))?;
|
||||
let client_ca_cert = std::fs::read_to_string(tls_dir.join("ca.pem"))?;
|
||||
let client_ca_cert = Certificate::from_pem(client_ca_cert);
|
||||
let server_identity = Identity::from_pem(cert, key);
|
||||
let tls_config = ServerTlsConfig::new()
|
||||
.identity(server_identity)
|
||||
.client_ca_root(client_ca_cert);
|
||||
|
||||
Server::builder()
|
||||
.tls_config(tls_config)?
|
||||
.add_service(CdkMintServer::new(self.clone()))
|
||||
}
|
||||
None => {
|
||||
tracing::warn!("No valid TLS configuration found, starting insecure server");
|
||||
Server::builder().add_service(CdkMintServer::new(self.clone()))
|
||||
}
|
||||
};
|
||||
|
||||
let shutdown = self.shutdown.clone();
|
||||
let addr = self.socket_addr;
|
||||
|
||||
self.handle = Some(Arc::new(tokio::spawn(async move {
|
||||
let server = server.serve_with_shutdown(addr, async {
|
||||
shutdown.notified().await;
|
||||
});
|
||||
|
||||
server.await?;
|
||||
Ok(())
|
||||
})));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop(&self) -> Result<(), Error> {
|
||||
self.shutdown.notify_one();
|
||||
if let Some(handle) = &self.handle {
|
||||
while !handle.is_finished() {
|
||||
tracing::info!("Waitning for mint rpc server to stop");
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Mint rpc server stopped");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MintRPCServer {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("Dropping mint rpc server");
|
||||
self.shutdown.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl CdkMint for MintRPCServer {
|
||||
async fn get_info(
|
||||
&self,
|
||||
_request: Request<GetInfoRequest>,
|
||||
) -> Result<Response<GetInfoResponse>, Status> {
|
||||
let info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
let total_issued = self
|
||||
.mint
|
||||
.total_issued()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
let total_issued: Amount = Amount::try_sum(total_issued.values().cloned())
|
||||
.map_err(|_| Status::internal("Overflow".to_string()))?;
|
||||
|
||||
let total_redeemed = self
|
||||
.mint
|
||||
.total_redeemed()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
let total_redeemed: Amount = Amount::try_sum(total_redeemed.values().cloned())
|
||||
.map_err(|_| Status::internal("Overflow".to_string()))?;
|
||||
|
||||
let contact = info
|
||||
.contact
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|c| ContactInfo {
|
||||
method: c.method,
|
||||
info: c.info,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Response::new(GetInfoResponse {
|
||||
name: info.name,
|
||||
description: info.description,
|
||||
long_description: info.description_long,
|
||||
version: info.version.map(|v| v.to_string()),
|
||||
contact,
|
||||
motd: info.motd,
|
||||
icon_url: info.icon_url,
|
||||
urls: info.urls.unwrap_or_default(),
|
||||
total_issued: total_issued.into(),
|
||||
total_redeemed: total_redeemed.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn update_motd(
|
||||
&self,
|
||||
request: Request<UpdateMotdRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let motd = request.into_inner().motd;
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
info.motd = Some(motd);
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_short_description(
|
||||
&self,
|
||||
request: Request<UpdateDescriptionRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let description = request.into_inner().description;
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
info.description = Some(description);
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_long_description(
|
||||
&self,
|
||||
request: Request<UpdateDescriptionRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let description = request.into_inner().description;
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
info.description = Some(description);
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_name(
|
||||
&self,
|
||||
request: Request<UpdateNameRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let name = request.into_inner().name;
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
info.name = Some(name);
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_icon_url(
|
||||
&self,
|
||||
request: Request<UpdateIconUrlRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let icon_url = request.into_inner().icon_url;
|
||||
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
info.icon_url = Some(icon_url);
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn add_url(
|
||||
&self,
|
||||
request: Request<UpdateUrlRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let url = request.into_inner().url;
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
let urls = info.urls;
|
||||
urls.clone().unwrap_or_default().push(url);
|
||||
|
||||
info.urls = urls;
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn remove_url(
|
||||
&self,
|
||||
request: Request<UpdateUrlRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let url = request.into_inner().url;
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
let urls = info.urls;
|
||||
urls.clone().unwrap_or_default().push(url);
|
||||
|
||||
info.urls = urls;
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn add_contact(
|
||||
&self,
|
||||
request: Request<UpdateContactRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let request_inner = request.into_inner();
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
info.contact
|
||||
.get_or_insert_with(Vec::new)
|
||||
.push(cdk::nuts::ContactInfo::new(
|
||||
request_inner.method,
|
||||
request_inner.info,
|
||||
));
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
async fn remove_contact(
|
||||
&self,
|
||||
request: Request<UpdateContactRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let request_inner = request.into_inner();
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
if let Some(contact) = info.contact.as_mut() {
|
||||
let contact_info =
|
||||
cdk::nuts::ContactInfo::new(request_inner.method, request_inner.info);
|
||||
contact.retain(|x| x != &contact_info);
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
}
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_nut04(
|
||||
&self,
|
||||
request: Request<UpdateNut04Request>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
let mut nut04_settings = info.nuts.nut04.clone();
|
||||
|
||||
let request_inner = request.into_inner();
|
||||
|
||||
let unit = CurrencyUnit::from_str(&request_inner.unit)
|
||||
.map_err(|_| Status::invalid_argument("Invalid unit".to_string()))?;
|
||||
|
||||
let payment_method = PaymentMethod::from_str(&request_inner.method)
|
||||
.map_err(|_| Status::invalid_argument("Invalid method".to_string()))?;
|
||||
|
||||
let current_nut04_settings = nut04_settings.remove_settings(&unit, &payment_method);
|
||||
|
||||
let mut methods = nut04_settings.methods.clone();
|
||||
|
||||
let updated_method_settings = MintMethodSettings {
|
||||
method: payment_method,
|
||||
unit,
|
||||
min_amount: request_inner
|
||||
.min
|
||||
.map(Amount::from)
|
||||
.or_else(|| current_nut04_settings.as_ref().and_then(|s| s.min_amount)),
|
||||
max_amount: request_inner
|
||||
.max
|
||||
.map(Amount::from)
|
||||
.or_else(|| current_nut04_settings.as_ref().and_then(|s| s.max_amount)),
|
||||
description: request_inner.description.unwrap_or(
|
||||
current_nut04_settings
|
||||
.map(|c| c.description)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
};
|
||||
|
||||
methods.push(updated_method_settings);
|
||||
|
||||
nut04_settings.methods = methods;
|
||||
|
||||
if let Some(disabled) = request_inner.disabled {
|
||||
nut04_settings.disabled = disabled;
|
||||
}
|
||||
|
||||
info.nuts.nut04 = nut04_settings;
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_nut05(
|
||||
&self,
|
||||
request: Request<UpdateNut05Request>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let mut info = self
|
||||
.mint
|
||||
.mint_info()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
let mut nut05_settings = info.nuts.nut05.clone();
|
||||
|
||||
let request_inner = request.into_inner();
|
||||
|
||||
let unit = CurrencyUnit::from_str(&request_inner.unit)
|
||||
.map_err(|_| Status::invalid_argument("Invalid unit".to_string()))?;
|
||||
|
||||
let payment_method = PaymentMethod::from_str(&request_inner.method)
|
||||
.map_err(|_| Status::invalid_argument("Invalid method".to_string()))?;
|
||||
|
||||
let current_nut05_settings = nut05_settings.remove_settings(&unit, &payment_method);
|
||||
|
||||
let mut methods = nut05_settings.methods;
|
||||
|
||||
let updated_method_settings = MeltMethodSettings {
|
||||
method: payment_method,
|
||||
unit,
|
||||
min_amount: request_inner
|
||||
.min
|
||||
.map(Amount::from)
|
||||
.or_else(|| current_nut05_settings.as_ref().and_then(|s| s.min_amount)),
|
||||
max_amount: request_inner
|
||||
.max
|
||||
.map(Amount::from)
|
||||
.or_else(|| current_nut05_settings.as_ref().and_then(|s| s.max_amount)),
|
||||
};
|
||||
|
||||
methods.push(updated_method_settings);
|
||||
nut05_settings.methods = methods;
|
||||
|
||||
if let Some(disabled) = request_inner.disabled {
|
||||
nut05_settings.disabled = disabled;
|
||||
}
|
||||
|
||||
info.nuts.nut05 = nut05_settings;
|
||||
|
||||
self.mint
|
||||
.set_mint_info(info)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_quote_ttl(
|
||||
&self,
|
||||
request: Request<UpdateQuoteTtlRequest>,
|
||||
) -> Result<Response<UpdateResponse>, Status> {
|
||||
let current_ttl = self
|
||||
.mint
|
||||
.quote_ttl()
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
let request = request.into_inner();
|
||||
|
||||
let quote_ttl = QuoteTTL {
|
||||
mint_ttl: request.mint_ttl.unwrap_or(current_ttl.mint_ttl),
|
||||
melt_ttl: request.melt_ttl.unwrap_or(current_ttl.melt_ttl),
|
||||
};
|
||||
|
||||
self.mint
|
||||
.set_quote_ttl(quote_ttl)
|
||||
.await
|
||||
.map_err(|err| Status::internal(err.to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateResponse {}))
|
||||
}
|
||||
|
||||
async fn update_nut04_quote(
|
||||
&self,
|
||||
request: Request<UpdateNut04QuoteRequest>,
|
||||
) -> Result<Response<UpdateNut04QuoteRequest>, Status> {
|
||||
let request = request.into_inner();
|
||||
let quote_id = request
|
||||
.quote_id
|
||||
.parse()
|
||||
.map_err(|_| Status::invalid_argument("Invalid quote id".to_string()))?;
|
||||
|
||||
let state = MintQuoteState::from_str(&request.state)
|
||||
.map_err(|_| Status::invalid_argument("Invalid quote state".to_string()))?;
|
||||
|
||||
let mint_quote = self
|
||||
.mint
|
||||
.localstore
|
||||
.get_mint_quote("e_id)
|
||||
.await
|
||||
.map_err(|_| Status::invalid_argument("Could not find quote".to_string()))?
|
||||
.ok_or(Status::invalid_argument("Could not find quote".to_string()))?;
|
||||
|
||||
match state {
|
||||
MintQuoteState::Paid => {
|
||||
self.mint
|
||||
.pay_mint_quote(&mint_quote)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Could not find quote".to_string()))?;
|
||||
}
|
||||
_ => {
|
||||
let mut mint_quote = mint_quote;
|
||||
|
||||
mint_quote.state = state;
|
||||
|
||||
self.mint
|
||||
.update_mint_quote(mint_quote)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Could not update quote".to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
let mint_quote = self
|
||||
.mint
|
||||
.localstore
|
||||
.get_mint_quote("e_id)
|
||||
.await
|
||||
.map_err(|_| Status::invalid_argument("Could not find quote".to_string()))?
|
||||
.ok_or(Status::invalid_argument("Could not find quote".to_string()))?;
|
||||
|
||||
Ok(Response::new(UpdateNut04QuoteRequest {
|
||||
state: mint_quote.state.to_string(),
|
||||
quote_id: mint_quote.id.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn rotate_next_keyset(
|
||||
&self,
|
||||
request: Request<RotateNextKeysetRequest>,
|
||||
) -> Result<Response<RotateNextKeysetResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let unit = CurrencyUnit::from_str(&request.unit)
|
||||
.map_err(|_| Status::invalid_argument("Invalid unit".to_string()))?;
|
||||
|
||||
let keyset_info = self
|
||||
.mint
|
||||
.rotate_next_keyset(
|
||||
unit,
|
||||
request.max_order.map(|a| a as u8).unwrap_or(32),
|
||||
request.input_fee_ppk.unwrap_or(0),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Status::invalid_argument("Could not rotate keyset".to_string()))?;
|
||||
|
||||
Ok(Response::new(RotateNextKeysetResponse {
|
||||
id: keyset_info.id.to_string(),
|
||||
unit: keyset_info.unit.to_string(),
|
||||
max_order: keyset_info.max_order.into(),
|
||||
input_fee_ppk: keyset_info.input_fee_ppk,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,14 @@ authors = ["CDK Developers"]
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/cashubtc/cdk"
|
||||
repository = "https://github.com/cashubtc/cdk.git"
|
||||
rust-version = "1.63.0" # MSRV
|
||||
description = "CDK mint binary"
|
||||
|
||||
[features]
|
||||
default = ["management-rpc"]
|
||||
swagger = ["cdk-axum/swagger", "dep:utoipa", "dep:utoipa-swagger-ui"]
|
||||
redis = ["cdk-axum/redis"]
|
||||
management-rpc = ["cdk-mint-rpc"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
axum = "0.6.20"
|
||||
@@ -28,8 +33,9 @@ cdk-lnd = { path = "../cdk-lnd", version = "0.6.0", default-features = false }
|
||||
cdk-fake-wallet = { path = "../cdk-fake-wallet", version = "0.6.0", default-features = false }
|
||||
cdk-strike = { path = "../cdk-strike", version = "0.6.0" }
|
||||
cdk-axum = { path = "../cdk-axum", version = "0.6.0", default-features = false }
|
||||
cdk-mint-rpc = { path = "../cdk-mint-rpc", version = "0.6.0", default-features = false, optional = true }
|
||||
config = { version = "0.13.3", features = ["toml"] }
|
||||
clap = { version = "4.4.8", features = ["derive", "env", "default"] }
|
||||
clap = { version = "~4.0.32", features = ["derive"] }
|
||||
tokio = { version = "1", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = [
|
||||
"attributes",
|
||||
@@ -38,14 +44,10 @@ tracing = { version = "0.1", default-features = false, features = [
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
futures = { version = "0.3.28", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
bip39 = "2.0"
|
||||
bip39 = { version = "2.0", features = ["rand"] }
|
||||
tower-http = { version = "0.4.4", features = ["cors", "compression-full"] }
|
||||
lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
|
||||
home = "0.5.5"
|
||||
url = "2.3"
|
||||
utoipa = { version = "4", optional = true }
|
||||
utoipa-swagger-ui = { version = "4", features = ["axum"], optional = true }
|
||||
|
||||
[features]
|
||||
swagger = ["cdk-axum/swagger", "dep:utoipa", "dep:utoipa-swagger-ui"]
|
||||
redis = ["cdk-axum/redis"]
|
||||
|
||||
@@ -6,6 +6,12 @@ mnemonic = ""
|
||||
# input_fee_ppk = 0
|
||||
# enable_swagger_ui = false
|
||||
|
||||
[mint_management_rpc]
|
||||
enabled = true
|
||||
# address = "127.0.0.1"
|
||||
# port = 8086
|
||||
|
||||
|
||||
[info.http_cache]
|
||||
# memory or redis
|
||||
backend = "memory"
|
||||
@@ -15,7 +21,8 @@ tti = 60
|
||||
# key_prefix = "mintd"
|
||||
# connection_string = "redis://localhost"
|
||||
|
||||
|
||||
# NOTE: If [mint_management_rpc] is enabled these values will only be used on first start up.
|
||||
# Further changes must be made through the rpc.
|
||||
[mint_info]
|
||||
# name = "cdk-mintd mutiney net mint"
|
||||
# Hex pubkey of mint
|
||||
|
||||
@@ -187,6 +187,8 @@ pub struct Settings {
|
||||
pub lnd: Option<Lnd>,
|
||||
pub fake_wallet: Option<FakeWallet>,
|
||||
pub database: Database,
|
||||
#[cfg(feature = "management-rpc")]
|
||||
pub mint_management_rpc: Option<MintManagementRpc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@@ -209,6 +211,17 @@ pub struct MintInfo {
|
||||
pub contact_email: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct MintManagementRpc {
|
||||
/// When this is set to `true` the mint use the config file for the initial set up on first start.
|
||||
/// Changes to the `[mint_info]` after this **MUST** be made via the RPC changes to the config file or env vars will be ignored.
|
||||
pub enabled: bool,
|
||||
pub address: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub tls_dir_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
#[must_use]
|
||||
pub fn new<P>(config_file_name: Option<P>) -> Self
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::str::FromStr;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use cdk::nuts::CurrencyUnit;
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
use crate::config::MintManagementRpc;
|
||||
use crate::config::{
|
||||
Cln, Database, DatabaseEngine, FakeWallet, Info, LNbits, Ln, LnBackend, Lnd, MintInfo,
|
||||
Phoenixd, Settings, Strike,
|
||||
@@ -70,6 +72,15 @@ pub const ENV_FAKE_WALLET_FEE_PERCENT: &str = "CDK_MINTD_FAKE_WALLET_FEE_PERCENT
|
||||
pub const ENV_FAKE_WALLET_RESERVE_FEE_MIN: &str = "CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN";
|
||||
pub const ENV_FAKE_WALLET_MIN_DELAY: &str = "CDK_MINTD_FAKE_WALLET_MIN_DELAY";
|
||||
pub const ENV_FAKE_WALLET_MAX_DELAY: &str = "CDK_MINTD_FAKE_WALLET_MAX_DELAY";
|
||||
// Mint RPC Server
|
||||
#[cfg(feature = "management-rpc")]
|
||||
pub const ENV_MINT_MANAGEMENT_ENABLED: &str = "CDK_MINTD_MINT_MANAGEMENT_ENABLED";
|
||||
#[cfg(feature = "management-rpc")]
|
||||
pub const ENV_MINT_MANAGEMENT_ADDRESS: &str = "CDK_MINTD_MANAGEMENT_ADDRESS";
|
||||
#[cfg(feature = "management-rpc")]
|
||||
pub const ENV_MINT_MANAGEMENT_PORT: &str = "CDK_MINTD_MANAGEMENT_PORT";
|
||||
#[cfg(feature = "management-rpc")]
|
||||
pub const ENV_MINT_MANAGEMENT_TLS_DIR_PATH: &str = "CDK_MINTD_MANAGEMENT_TLS_DIR_PATH";
|
||||
|
||||
impl Settings {
|
||||
pub fn from_env(&mut self) -> Result<Self> {
|
||||
@@ -82,6 +93,16 @@ impl Settings {
|
||||
self.mint_info = self.mint_info.clone().from_env();
|
||||
self.ln = self.ln.clone().from_env();
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
{
|
||||
self.mint_management_rpc = Some(
|
||||
self.mint_management_rpc
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.from_env(),
|
||||
);
|
||||
}
|
||||
|
||||
match self.ln.ln_backend {
|
||||
LnBackend::Cln => {
|
||||
self.cln = Some(self.cln.clone().unwrap_or_default().from_env());
|
||||
@@ -430,3 +451,30 @@ impl FakeWallet {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
impl MintManagementRpc {
|
||||
pub fn from_env(mut self) -> Self {
|
||||
if let Ok(enabled) = env::var(ENV_MINT_MANAGEMENT_ENABLED) {
|
||||
if let Ok(enabled) = enabled.parse() {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(address) = env::var(ENV_MINT_MANAGEMENT_ADDRESS) {
|
||||
self.address = Some(address);
|
||||
}
|
||||
|
||||
if let Ok(port) = env::var(ENV_MINT_MANAGEMENT_PORT) {
|
||||
if let Ok(port) = port.parse::<u16>() {
|
||||
self.port = Some(port);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(tls_path) = env::var(ENV_MINT_MANAGEMENT_TLS_DIR_PATH) {
|
||||
self.tls_dir_path = Some(tls_path.into());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path}
|
||||
use cdk::nuts::{ContactInfo, CurrencyUnit, MintVersion, PaymentMethod};
|
||||
use cdk::types::LnKey;
|
||||
use cdk_axum::cache::HttpCache;
|
||||
#[cfg(feature = "management-rpc")]
|
||||
use cdk_mint_rpc::MintRPCServer;
|
||||
use cdk_mintd::cli::CLIArgs;
|
||||
use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
|
||||
use cdk_mintd::env_vars::ENV_WORK_DIR;
|
||||
@@ -46,10 +48,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let sqlx_filter = "sqlx=warn";
|
||||
let hyper_filter = "hyper=warn";
|
||||
let h2_filter = "h2=warn";
|
||||
|
||||
let env_filter = EnvFilter::new(format!(
|
||||
"{},{},{}",
|
||||
default_filter, sqlx_filter, hyper_filter
|
||||
"{},{},{},{}",
|
||||
default_filter, sqlx_filter, hyper_filter, h2_filter
|
||||
));
|
||||
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
@@ -303,7 +306,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
.with_name(settings.mint_info.name)
|
||||
.with_version(mint_version)
|
||||
.with_description(settings.mint_info.description)
|
||||
.with_quote_ttl(10000, 10000)
|
||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||
|
||||
let cached_endpoints = vec![
|
||||
@@ -358,12 +360,51 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
let shutdown = Arc::new(Notify::new());
|
||||
|
||||
let mint_clone = Arc::clone(&mint);
|
||||
tokio::spawn({
|
||||
let shutdown = Arc::clone(&shutdown);
|
||||
async move { mint.wait_for_paid_invoices(shutdown).await }
|
||||
async move { mint_clone.wait_for_paid_invoices(shutdown).await }
|
||||
});
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
let mut rpc_enabled = false;
|
||||
#[cfg(not(feature = "management-rpc"))]
|
||||
let rpc_enabled = false;
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
{
|
||||
if let Some(rpc_settings) = settings.mint_management_rpc {
|
||||
if rpc_settings.enabled {
|
||||
let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
|
||||
let port = rpc_settings.port.unwrap_or(8086);
|
||||
let mut mint_rpc = MintRPCServer::new(&addr, port, mint.clone())?;
|
||||
|
||||
let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
|
||||
|
||||
mint_rpc.start(Some(tls_dir)).await?;
|
||||
|
||||
rpc_server = Some(mint_rpc);
|
||||
|
||||
rpc_enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rpc_enabled {
|
||||
if mint.mint_info().await.is_err() {
|
||||
tracing::info!("Mint info not set on mint, setting.");
|
||||
mint.set_mint_info(mint_builder.mint_info).await?;
|
||||
} else {
|
||||
tracing::info!("Mint info already set, not using config file settings.");
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("RPC not enabled, using mint info from config.");
|
||||
mint.set_mint_info(mint_builder.mint_info).await?;
|
||||
}
|
||||
|
||||
let axum_result = axum::Server::bind(
|
||||
&format!("{}:{}", listen_addr, listen_port)
|
||||
.as_str()
|
||||
@@ -374,6 +415,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
shutdown.notify_waiters();
|
||||
|
||||
#[cfg(feature = "management-rpc")]
|
||||
{
|
||||
if let Some(rpc_server) = rpc_server {
|
||||
rpc_server.stop().await?;
|
||||
}
|
||||
}
|
||||
|
||||
match axum_result {
|
||||
Ok(_) => {
|
||||
tracing::info!("Axum server stopped with okay status");
|
||||
|
||||
@@ -16,19 +16,18 @@ use crate::nuts::{
|
||||
ContactInfo, CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings, MintVersion,
|
||||
MppMethodSettings, PaymentMethod,
|
||||
};
|
||||
use crate::types::{LnKey, QuoteTTL};
|
||||
use crate::types::LnKey;
|
||||
|
||||
/// Cashu Mint
|
||||
#[derive(Default)]
|
||||
pub struct MintBuilder {
|
||||
/// Mint Info
|
||||
mint_info: MintInfo,
|
||||
pub mint_info: MintInfo,
|
||||
/// Mint Storage backend
|
||||
localstore: Option<Arc<dyn MintDatabase<Err = database::Error> + Send + Sync>>,
|
||||
/// Ln backends for mint
|
||||
ln: Option<HashMap<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>>,
|
||||
seed: Option<Vec<u8>>,
|
||||
quote_ttl: Option<QuoteTTL>,
|
||||
supported_units: HashMap<CurrencyUnit, (u64, u8)>,
|
||||
}
|
||||
|
||||
@@ -175,15 +174,6 @@ impl MintBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set quote ttl
|
||||
pub fn with_quote_ttl(mut self, mint_ttl: u64, melt_ttl: u64) -> Self {
|
||||
let quote_ttl = QuoteTTL { mint_ttl, melt_ttl };
|
||||
|
||||
self.quote_ttl = Some(quote_ttl);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set pubkey
|
||||
pub fn with_pubkey(mut self, pubkey: crate::nuts::PublicKey) -> Self {
|
||||
self.mint_info.pubkey = Some(pubkey);
|
||||
@@ -222,11 +212,6 @@ impl MintBuilder {
|
||||
.localstore
|
||||
.clone()
|
||||
.ok_or(anyhow!("Localstore not set"))?;
|
||||
localstore.set_mint_info(self.mint_info.clone()).await?;
|
||||
|
||||
localstore
|
||||
.set_quote_ttl(self.quote_ttl.ok_or(anyhow!("Quote ttl not set"))?)
|
||||
.await?;
|
||||
|
||||
Ok(Mint::new(
|
||||
self.seed.as_ref().ok_or(anyhow!("Mint seed not set"))?,
|
||||
|
||||
@@ -89,7 +89,7 @@ impl Mint {
|
||||
|
||||
/// Add current keyset to inactive keysets
|
||||
/// Generate new keyset
|
||||
#[instrument(skip(self))]
|
||||
#[instrument(skip(self, custom_paths))]
|
||||
pub async fn rotate_keyset(
|
||||
&self,
|
||||
unit: CurrencyUnit,
|
||||
@@ -115,11 +115,13 @@ impl Mint {
|
||||
);
|
||||
let id = keyset_info.id;
|
||||
self.localstore.add_keyset_info(keyset_info.clone()).await?;
|
||||
self.localstore.set_active_keyset(unit, id).await?;
|
||||
self.localstore.set_active_keyset(unit.clone(), id).await?;
|
||||
|
||||
let mut keysets = self.keysets.write().await;
|
||||
keysets.insert(id, keyset);
|
||||
|
||||
tracing::info!("Rotated to new keyset {} for {}", id, unit);
|
||||
|
||||
Ok(keyset_info)
|
||||
}
|
||||
|
||||
|
||||
@@ -216,45 +216,45 @@ impl Mint {
|
||||
.get_mint_quote_by_request_lookup_id(request_lookup_id)
|
||||
.await
|
||||
{
|
||||
tracing::debug!(
|
||||
"Received payment notification for mint quote {}",
|
||||
mint_quote.id
|
||||
);
|
||||
if mint_quote.state != MintQuoteState::Issued
|
||||
&& mint_quote.state != MintQuoteState::Paid
|
||||
{
|
||||
let unix_time = unix_time();
|
||||
self.pay_mint_quote(&mint_quote).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if mint_quote.expiry < unix_time {
|
||||
tracing::warn!(
|
||||
"Mint quote {} paid at {} expired at {}, leaving current state",
|
||||
mint_quote.id,
|
||||
mint_quote.expiry,
|
||||
unix_time,
|
||||
);
|
||||
return Err(Error::ExpiredQuote(mint_quote.expiry, unix_time));
|
||||
}
|
||||
/// Mark mint quote as paid
|
||||
#[instrument(skip_all)]
|
||||
pub async fn pay_mint_quote(&self, mint_quote: &MintQuote) -> Result<(), Error> {
|
||||
tracing::debug!(
|
||||
"Received payment notification for mint quote {}",
|
||||
mint_quote.id
|
||||
);
|
||||
if mint_quote.state != MintQuoteState::Issued && mint_quote.state != MintQuoteState::Paid {
|
||||
let unix_time = unix_time();
|
||||
|
||||
tracing::debug!(
|
||||
"Marking quote {} paid by lookup id {}",
|
||||
if mint_quote.expiry < unix_time {
|
||||
tracing::warn!(
|
||||
"Mint quote {} paid at {} expired at {}, leaving current state",
|
||||
mint_quote.id,
|
||||
request_lookup_id
|
||||
);
|
||||
|
||||
self.localstore
|
||||
.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
|
||||
.await?;
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"{} Quote already {} continuing",
|
||||
mint_quote.id,
|
||||
mint_quote.state
|
||||
mint_quote.expiry,
|
||||
unix_time,
|
||||
);
|
||||
return Err(Error::ExpiredQuote(mint_quote.expiry, unix_time));
|
||||
}
|
||||
|
||||
self.pubsub_manager
|
||||
.mint_quote_bolt11_status(mint_quote, MintQuoteState::Paid);
|
||||
self.localstore
|
||||
.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
|
||||
.await?;
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"{} Quote already {} continuing",
|
||||
mint_quote.id,
|
||||
mint_quote.state
|
||||
);
|
||||
}
|
||||
|
||||
self.pubsub_manager
|
||||
.mint_quote_bolt11_status(mint_quote.clone(), MintQuoteState::Paid);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
|
||||
use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv};
|
||||
use bitcoin::secp256k1::{self, Secp256k1};
|
||||
use cdk_common::common::LnKey;
|
||||
use cdk_common::common::{LnKey, QuoteTTL};
|
||||
use cdk_common::database::{self, MintDatabase};
|
||||
use cdk_common::mint::MintKeySetInfo;
|
||||
use futures::StreamExt;
|
||||
@@ -194,6 +194,21 @@ impl Mint {
|
||||
Ok(self.localstore.get_mint_info().await?)
|
||||
}
|
||||
|
||||
/// Set mint info
|
||||
pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
|
||||
Ok(self.localstore.set_mint_info(mint_info).await?)
|
||||
}
|
||||
|
||||
/// Get quote ttl
|
||||
pub async fn quote_ttl(&self) -> Result<QuoteTTL, Error> {
|
||||
Ok(self.localstore.get_quote_ttl().await?)
|
||||
}
|
||||
|
||||
/// Set quote ttl
|
||||
pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
|
||||
Ok(self.localstore.set_quote_ttl(quote_ttl).await?)
|
||||
}
|
||||
|
||||
/// Wait for any invoice to be paid
|
||||
/// For each backend starts a task that waits for any invoice to be paid
|
||||
/// Once invoice is paid mint quote status is updated
|
||||
|
||||
@@ -244,6 +244,13 @@
|
||||
cargo update -p async-compression --precise 0.4.3
|
||||
cargo update -p zstd-sys --precise 2.0.8+zstd.1.5.5
|
||||
|
||||
cargo update -p clap_lex --precise 0.3.0
|
||||
cargo update -p regex --precise 1.9.6
|
||||
cargo update -p petgraph --precise 0.6.2
|
||||
cargo update -p hashbrown@0.15.2 --precise 0.15.0
|
||||
cargo update -p async-stream --precise 0.3.5
|
||||
cargo update -p home --precise 0.5.5
|
||||
|
||||
# For wasm32-unknown-unknown target
|
||||
cargo update -p bumpalo --precise 3.12.0
|
||||
cargo update -p moka --precise 0.11.1
|
||||
@@ -291,6 +298,7 @@
|
||||
export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [
|
||||
pkgs.zlib
|
||||
]}:$LD_LIBRARY_PATH
|
||||
export RUST_SRC_PATH=${nightly_toolchain}/lib/rustlib/src/rust/library
|
||||
'';
|
||||
buildInputs = buildInputs ++ [ nightly_toolchain ];
|
||||
inherit nativeBuildInputs;
|
||||
|
||||
Reference in New Issue
Block a user