mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-18 13:14:59 +01:00
Split uniffi types into multiple mods (#1142)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
166
crates/cdk-ffi/src/types/amount.rs
Normal file
166
crates/cdk-ffi/src/types/amount.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
//! Amount and currency related types
|
||||
|
||||
use cdk::nuts::CurrencyUnit as CdkCurrencyUnit;
|
||||
use cdk::Amount as CdkAmount;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible Amount type
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct Amount {
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
impl Amount {
|
||||
pub fn new(value: u64) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self { value: 0 }
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.value == 0
|
||||
}
|
||||
|
||||
pub fn convert_unit(
|
||||
&self,
|
||||
current_unit: CurrencyUnit,
|
||||
target_unit: CurrencyUnit,
|
||||
) -> Result<Amount, FfiError> {
|
||||
Ok(CdkAmount::from(self.value)
|
||||
.convert_unit(¤t_unit.into(), &target_unit.into())
|
||||
.map(Into::into)?)
|
||||
}
|
||||
|
||||
pub fn add(&self, other: Amount) -> Result<Amount, FfiError> {
|
||||
let self_amount = CdkAmount::from(self.value);
|
||||
let other_amount = CdkAmount::from(other.value);
|
||||
self_amount
|
||||
.checked_add(other_amount)
|
||||
.map(Into::into)
|
||||
.ok_or(FfiError::AmountOverflow)
|
||||
}
|
||||
|
||||
pub fn subtract(&self, other: Amount) -> Result<Amount, FfiError> {
|
||||
let self_amount = CdkAmount::from(self.value);
|
||||
let other_amount = CdkAmount::from(other.value);
|
||||
self_amount
|
||||
.checked_sub(other_amount)
|
||||
.map(Into::into)
|
||||
.ok_or(FfiError::AmountOverflow)
|
||||
}
|
||||
|
||||
pub fn multiply(&self, factor: u64) -> Result<Amount, FfiError> {
|
||||
let self_amount = CdkAmount::from(self.value);
|
||||
let factor_amount = CdkAmount::from(factor);
|
||||
self_amount
|
||||
.checked_mul(factor_amount)
|
||||
.map(Into::into)
|
||||
.ok_or(FfiError::AmountOverflow)
|
||||
}
|
||||
|
||||
pub fn divide(&self, divisor: u64) -> Result<Amount, FfiError> {
|
||||
if divisor == 0 {
|
||||
return Err(FfiError::DivisionByZero);
|
||||
}
|
||||
let self_amount = CdkAmount::from(self.value);
|
||||
let divisor_amount = CdkAmount::from(divisor);
|
||||
self_amount
|
||||
.checked_div(divisor_amount)
|
||||
.map(Into::into)
|
||||
.ok_or(FfiError::AmountOverflow)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CdkAmount> for Amount {
|
||||
fn from(amount: CdkAmount) -> Self {
|
||||
Self {
|
||||
value: u64::from(amount),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Amount> for CdkAmount {
|
||||
fn from(amount: Amount) -> Self {
|
||||
CdkAmount::from(amount.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Currency Unit
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum CurrencyUnit {
|
||||
Sat,
|
||||
Msat,
|
||||
Usd,
|
||||
Eur,
|
||||
Auth,
|
||||
Custom { unit: String },
|
||||
}
|
||||
|
||||
impl From<CdkCurrencyUnit> for CurrencyUnit {
|
||||
fn from(unit: CdkCurrencyUnit) -> Self {
|
||||
match unit {
|
||||
CdkCurrencyUnit::Sat => CurrencyUnit::Sat,
|
||||
CdkCurrencyUnit::Msat => CurrencyUnit::Msat,
|
||||
CdkCurrencyUnit::Usd => CurrencyUnit::Usd,
|
||||
CdkCurrencyUnit::Eur => CurrencyUnit::Eur,
|
||||
CdkCurrencyUnit::Auth => CurrencyUnit::Auth,
|
||||
CdkCurrencyUnit::Custom(s) => CurrencyUnit::Custom { unit: s },
|
||||
_ => CurrencyUnit::Sat, // Default for unknown units
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CurrencyUnit> for CdkCurrencyUnit {
|
||||
fn from(unit: CurrencyUnit) -> Self {
|
||||
match unit {
|
||||
CurrencyUnit::Sat => CdkCurrencyUnit::Sat,
|
||||
CurrencyUnit::Msat => CdkCurrencyUnit::Msat,
|
||||
CurrencyUnit::Usd => CdkCurrencyUnit::Usd,
|
||||
CurrencyUnit::Eur => CdkCurrencyUnit::Eur,
|
||||
CurrencyUnit::Auth => CdkCurrencyUnit::Auth,
|
||||
CurrencyUnit::Custom { unit } => CdkCurrencyUnit::Custom(unit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible SplitTarget
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum SplitTarget {
|
||||
/// Default target; least amount of proofs
|
||||
None,
|
||||
/// Target amount for wallet to have most proofs that add up to value
|
||||
Value { amount: Amount },
|
||||
/// Specific amounts to split into (must equal amount being split)
|
||||
Values { amounts: Vec<Amount> },
|
||||
}
|
||||
|
||||
impl From<SplitTarget> for cdk::amount::SplitTarget {
|
||||
fn from(target: SplitTarget) -> Self {
|
||||
match target {
|
||||
SplitTarget::None => cdk::amount::SplitTarget::None,
|
||||
SplitTarget::Value { amount } => cdk::amount::SplitTarget::Value(amount.into()),
|
||||
SplitTarget::Values { amounts } => {
|
||||
cdk::amount::SplitTarget::Values(amounts.into_iter().map(Into::into).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::amount::SplitTarget> for SplitTarget {
|
||||
fn from(target: cdk::amount::SplitTarget) -> Self {
|
||||
match target {
|
||||
cdk::amount::SplitTarget::None => SplitTarget::None,
|
||||
cdk::amount::SplitTarget::Value(amount) => SplitTarget::Value {
|
||||
amount: amount.into(),
|
||||
},
|
||||
cdk::amount::SplitTarget::Values(amounts) => SplitTarget::Values {
|
||||
amounts: amounts.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
258
crates/cdk-ffi/src/types/keys.rs
Normal file
258
crates/cdk-ffi/src/types/keys.rs
Normal file
@@ -0,0 +1,258 @@
|
||||
//! Key-related FFI types
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::amount::CurrencyUnit;
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible KeySetInfo
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct KeySetInfo {
|
||||
pub id: String,
|
||||
pub unit: CurrencyUnit,
|
||||
pub active: bool,
|
||||
/// Input fee per thousand (ppk)
|
||||
pub input_fee_ppk: u64,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::KeySetInfo> for KeySetInfo {
|
||||
fn from(keyset: cdk::nuts::KeySetInfo) -> Self {
|
||||
Self {
|
||||
id: keyset.id.to_string(),
|
||||
unit: keyset.unit.into(),
|
||||
active: keyset.active,
|
||||
input_fee_ppk: keyset.input_fee_ppk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeySetInfo> for cdk::nuts::KeySetInfo {
|
||||
fn from(keyset: KeySetInfo) -> Self {
|
||||
use std::str::FromStr;
|
||||
Self {
|
||||
id: cdk::nuts::Id::from_str(&keyset.id).unwrap(),
|
||||
unit: keyset.unit.into(),
|
||||
active: keyset.active,
|
||||
final_expiry: None,
|
||||
input_fee_ppk: keyset.input_fee_ppk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySetInfo {
|
||||
/// Convert KeySetInfo to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode KeySetInfo from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_key_set_info(json: String) -> Result<KeySetInfo, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode KeySetInfo to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_key_set_info(info: KeySetInfo) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&info)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible PublicKey
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct PublicKey {
|
||||
/// Hex-encoded public key
|
||||
pub hex: String,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::PublicKey> for PublicKey {
|
||||
fn from(key: cdk::nuts::PublicKey) -> Self {
|
||||
Self {
|
||||
hex: key.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PublicKey> for cdk::nuts::PublicKey {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(key: PublicKey) -> Result<Self, Self::Error> {
|
||||
key.hex
|
||||
.parse()
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey {
|
||||
msg: format!("Invalid public key: {}", e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Keys (simplified - contains only essential info)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct Keys {
|
||||
/// Keyset ID
|
||||
pub id: String,
|
||||
/// Currency unit
|
||||
pub unit: CurrencyUnit,
|
||||
/// Map of amount to public key hex (simplified from BTreeMap)
|
||||
pub keys: HashMap<u64, String>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::Keys> for Keys {
|
||||
fn from(keys: cdk::nuts::Keys) -> Self {
|
||||
// Keys doesn't have id and unit - we'll need to get these from context
|
||||
// For now, use placeholder values
|
||||
Self {
|
||||
id: "unknown".to_string(), // This should come from KeySet
|
||||
unit: CurrencyUnit::Sat, // This should come from KeySet
|
||||
keys: keys
|
||||
.keys()
|
||||
.iter()
|
||||
.map(|(amount, pubkey)| (u64::from(*amount), pubkey.to_string()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Keys> for cdk::nuts::Keys {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(keys: Keys) -> Result<Self, Self::Error> {
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
// Convert the HashMap to BTreeMap with proper types
|
||||
let mut keys_map = BTreeMap::new();
|
||||
for (amount_u64, pubkey_hex) in keys.keys {
|
||||
let amount = cdk::Amount::from(amount_u64);
|
||||
let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
|
||||
keys_map.insert(amount, pubkey);
|
||||
}
|
||||
|
||||
Ok(cdk::nuts::Keys::new(keys_map))
|
||||
}
|
||||
}
|
||||
|
||||
impl Keys {
|
||||
/// Convert Keys to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode Keys from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_keys(json: String) -> Result<Keys, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode Keys to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_keys(keys: Keys) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&keys)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible KeySet
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct KeySet {
|
||||
/// Keyset ID
|
||||
pub id: String,
|
||||
/// Currency unit
|
||||
pub unit: CurrencyUnit,
|
||||
/// The keys (map of amount to public key hex)
|
||||
pub keys: HashMap<u64, String>,
|
||||
/// Optional expiry timestamp
|
||||
pub final_expiry: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::KeySet> for KeySet {
|
||||
fn from(keyset: cdk::nuts::KeySet) -> Self {
|
||||
Self {
|
||||
id: keyset.id.to_string(),
|
||||
unit: keyset.unit.into(),
|
||||
keys: keyset
|
||||
.keys
|
||||
.keys()
|
||||
.iter()
|
||||
.map(|(amount, pubkey)| (u64::from(*amount), pubkey.to_string()))
|
||||
.collect(),
|
||||
final_expiry: keyset.final_expiry,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<KeySet> for cdk::nuts::KeySet {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(keyset: KeySet) -> Result<Self, Self::Error> {
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
// Convert id
|
||||
let id = cdk::nuts::Id::from_str(&keyset.id)
|
||||
.map_err(|e| FfiError::Serialization { msg: e.to_string() })?;
|
||||
|
||||
// Convert unit
|
||||
let unit: cdk::nuts::CurrencyUnit = keyset.unit.into();
|
||||
|
||||
// Convert keys
|
||||
let mut keys_map = BTreeMap::new();
|
||||
for (amount_u64, pubkey_hex) in keyset.keys {
|
||||
let amount = cdk::Amount::from(amount_u64);
|
||||
let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
|
||||
keys_map.insert(amount, pubkey);
|
||||
}
|
||||
let keys = cdk::nuts::Keys::new(keys_map);
|
||||
|
||||
Ok(cdk::nuts::KeySet {
|
||||
id,
|
||||
unit,
|
||||
keys,
|
||||
final_expiry: keyset.final_expiry,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySet {
|
||||
/// Convert KeySet to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode KeySet from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_key_set(json: String) -> Result<KeySet, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode KeySet to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_key_set(keyset: KeySet) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&keyset)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible Id (for keyset IDs)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct Id {
|
||||
pub hex: String,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::Id> for Id {
|
||||
fn from(id: cdk::nuts::Id) -> Self {
|
||||
Self {
|
||||
hex: id.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for cdk::nuts::Id {
|
||||
fn from(id: Id) -> Self {
|
||||
use std::str::FromStr;
|
||||
Self::from_str(&id.hex).unwrap()
|
||||
}
|
||||
}
|
||||
675
crates/cdk-ffi/src/types/mint.rs
Normal file
675
crates/cdk-ffi/src/types/mint.rs
Normal file
@@ -0,0 +1,675 @@
|
||||
//! Mint-related FFI types
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::amount::{Amount, CurrencyUnit};
|
||||
use super::quote::PaymentMethod;
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible Mint URL
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct MintUrl {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl MintUrl {
|
||||
pub fn new(url: String) -> Result<Self, FfiError> {
|
||||
// Validate URL format
|
||||
url::Url::parse(&url).map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })?;
|
||||
|
||||
Ok(Self { url })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::mint_url::MintUrl> for MintUrl {
|
||||
fn from(mint_url: cdk::mint_url::MintUrl) -> Self {
|
||||
Self {
|
||||
url: mint_url.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MintUrl> for cdk::mint_url::MintUrl {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(mint_url: MintUrl) -> Result<Self, Self::Error> {
|
||||
cdk::mint_url::MintUrl::from_str(&mint_url.url)
|
||||
.map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible MintVersion
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct MintVersion {
|
||||
/// Mint Software name
|
||||
pub name: String,
|
||||
/// Mint Version
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::MintVersion> for MintVersion {
|
||||
fn from(version: cdk::nuts::MintVersion) -> Self {
|
||||
Self {
|
||||
name: version.name,
|
||||
version: version.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MintVersion> for cdk::nuts::MintVersion {
|
||||
fn from(version: MintVersion) -> Self {
|
||||
Self {
|
||||
name: version.name,
|
||||
version: version.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MintVersion {
|
||||
/// Convert MintVersion to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode MintVersion from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_mint_version(json: String) -> Result<MintVersion, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode MintVersion to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_mint_version(version: MintVersion) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&version)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible ContactInfo
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct ContactInfo {
|
||||
/// Contact Method i.e. nostr
|
||||
pub method: String,
|
||||
/// Contact info i.e. npub...
|
||||
pub info: String,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::ContactInfo> for ContactInfo {
|
||||
fn from(contact: cdk::nuts::ContactInfo) -> Self {
|
||||
Self {
|
||||
method: contact.method,
|
||||
info: contact.info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ContactInfo> for cdk::nuts::ContactInfo {
|
||||
fn from(contact: ContactInfo) -> Self {
|
||||
Self {
|
||||
method: contact.method,
|
||||
info: contact.info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContactInfo {
|
||||
/// Convert ContactInfo to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode ContactInfo from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_contact_info(json: String) -> Result<ContactInfo, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode ContactInfo to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_contact_info(info: ContactInfo) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&info)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible SupportedSettings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct SupportedSettings {
|
||||
/// Setting supported
|
||||
pub supported: bool,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut06::SupportedSettings> for SupportedSettings {
|
||||
fn from(settings: cdk::nuts::nut06::SupportedSettings) -> Self {
|
||||
Self {
|
||||
supported: settings.supported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SupportedSettings> for cdk::nuts::nut06::SupportedSettings {
|
||||
fn from(settings: SupportedSettings) -> Self {
|
||||
Self {
|
||||
supported: settings.supported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// NUT-04/05 FFI Types
|
||||
// -----------------------------
|
||||
|
||||
/// FFI-compatible MintMethodSettings (NUT-04)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct MintMethodSettings {
|
||||
pub method: PaymentMethod,
|
||||
pub unit: CurrencyUnit,
|
||||
pub min_amount: Option<Amount>,
|
||||
pub max_amount: Option<Amount>,
|
||||
/// For bolt11, whether mint supports setting invoice description
|
||||
pub description: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut04::MintMethodSettings> for MintMethodSettings {
|
||||
fn from(s: cdk::nuts::nut04::MintMethodSettings) -> Self {
|
||||
let description = match s.options {
|
||||
Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description }) => Some(description),
|
||||
_ => None,
|
||||
};
|
||||
Self {
|
||||
method: s.method.into(),
|
||||
unit: s.unit.into(),
|
||||
min_amount: s.min_amount.map(Into::into),
|
||||
max_amount: s.max_amount.map(Into::into),
|
||||
description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MintMethodSettings> for cdk::nuts::nut04::MintMethodSettings {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(s: MintMethodSettings) -> Result<Self, Self::Error> {
|
||||
let options = match (s.method.clone(), s.description) {
|
||||
(PaymentMethod::Bolt11, Some(description)) => {
|
||||
Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description })
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
Ok(Self {
|
||||
method: s.method.into(),
|
||||
unit: s.unit.into(),
|
||||
min_amount: s.min_amount.map(Into::into),
|
||||
max_amount: s.max_amount.map(Into::into),
|
||||
options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Nut04 Settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct Nut04Settings {
|
||||
pub methods: Vec<MintMethodSettings>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut04::Settings> for Nut04Settings {
|
||||
fn from(s: cdk::nuts::nut04::Settings) -> Self {
|
||||
Self {
|
||||
methods: s.methods.into_iter().map(Into::into).collect(),
|
||||
disabled: s.disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Nut04Settings> for cdk::nuts::nut04::Settings {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(s: Nut04Settings) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
methods: s
|
||||
.methods
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?,
|
||||
disabled: s.disabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible MeltMethodSettings (NUT-05)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct MeltMethodSettings {
|
||||
pub method: PaymentMethod,
|
||||
pub unit: CurrencyUnit,
|
||||
pub min_amount: Option<Amount>,
|
||||
pub max_amount: Option<Amount>,
|
||||
/// For bolt11, whether mint supports amountless invoices
|
||||
pub amountless: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut05::MeltMethodSettings> for MeltMethodSettings {
|
||||
fn from(s: cdk::nuts::nut05::MeltMethodSettings) -> Self {
|
||||
let amountless = match s.options {
|
||||
Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless }) => Some(amountless),
|
||||
_ => None,
|
||||
};
|
||||
Self {
|
||||
method: s.method.into(),
|
||||
unit: s.unit.into(),
|
||||
min_amount: s.min_amount.map(Into::into),
|
||||
max_amount: s.max_amount.map(Into::into),
|
||||
amountless,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MeltMethodSettings> for cdk::nuts::nut05::MeltMethodSettings {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(s: MeltMethodSettings) -> Result<Self, Self::Error> {
|
||||
let options = match (s.method.clone(), s.amountless) {
|
||||
(PaymentMethod::Bolt11, Some(amountless)) => {
|
||||
Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless })
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
Ok(Self {
|
||||
method: s.method.into(),
|
||||
unit: s.unit.into(),
|
||||
min_amount: s.min_amount.map(Into::into),
|
||||
max_amount: s.max_amount.map(Into::into),
|
||||
options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Nut05 Settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct Nut05Settings {
|
||||
pub methods: Vec<MeltMethodSettings>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut05::Settings> for Nut05Settings {
|
||||
fn from(s: cdk::nuts::nut05::Settings) -> Self {
|
||||
Self {
|
||||
methods: s.methods.into_iter().map(Into::into).collect(),
|
||||
disabled: s.disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Nut05Settings> for cdk::nuts::nut05::Settings {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(s: Nut05Settings) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
methods: s
|
||||
.methods
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?,
|
||||
disabled: s.disabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible ProtectedEndpoint (for auth nuts)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct ProtectedEndpoint {
|
||||
/// HTTP method (GET, POST, etc.)
|
||||
pub method: String,
|
||||
/// Endpoint path
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// FFI-compatible ClearAuthSettings (NUT-21)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct ClearAuthSettings {
|
||||
/// OpenID Connect discovery URL
|
||||
pub openid_discovery: String,
|
||||
/// OAuth 2.0 client ID
|
||||
pub client_id: String,
|
||||
/// Protected endpoints requiring clear authentication
|
||||
pub protected_endpoints: Vec<ProtectedEndpoint>,
|
||||
}
|
||||
|
||||
/// FFI-compatible BlindAuthSettings (NUT-22)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct BlindAuthSettings {
|
||||
/// Maximum number of blind auth tokens that can be minted per request
|
||||
pub bat_max_mint: u64,
|
||||
/// Protected endpoints requiring blind authentication
|
||||
pub protected_endpoints: Vec<ProtectedEndpoint>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::ClearAuthSettings> for ClearAuthSettings {
|
||||
fn from(settings: cdk::nuts::ClearAuthSettings) -> Self {
|
||||
Self {
|
||||
openid_discovery: settings.openid_discovery,
|
||||
client_id: settings.client_id,
|
||||
protected_endpoints: settings
|
||||
.protected_endpoints
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ClearAuthSettings> for cdk::nuts::ClearAuthSettings {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(settings: ClearAuthSettings) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
openid_discovery: settings.openid_discovery,
|
||||
client_id: settings.client_id,
|
||||
protected_endpoints: settings
|
||||
.protected_endpoints
|
||||
.into_iter()
|
||||
.map(|e| e.try_into())
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::BlindAuthSettings> for BlindAuthSettings {
|
||||
fn from(settings: cdk::nuts::BlindAuthSettings) -> Self {
|
||||
Self {
|
||||
bat_max_mint: settings.bat_max_mint,
|
||||
protected_endpoints: settings
|
||||
.protected_endpoints
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BlindAuthSettings> for cdk::nuts::BlindAuthSettings {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(settings: BlindAuthSettings) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
bat_max_mint: settings.bat_max_mint,
|
||||
protected_endpoints: settings
|
||||
.protected_endpoints
|
||||
.into_iter()
|
||||
.map(|e| e.try_into())
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::ProtectedEndpoint> for ProtectedEndpoint {
|
||||
fn from(endpoint: cdk::nuts::ProtectedEndpoint) -> Self {
|
||||
Self {
|
||||
method: match endpoint.method {
|
||||
cdk::nuts::Method::Get => "GET".to_string(),
|
||||
cdk::nuts::Method::Post => "POST".to_string(),
|
||||
},
|
||||
path: endpoint.path.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(endpoint: ProtectedEndpoint) -> Result<Self, Self::Error> {
|
||||
let method = match endpoint.method.as_str() {
|
||||
"GET" => cdk::nuts::Method::Get,
|
||||
"POST" => cdk::nuts::Method::Post,
|
||||
_ => {
|
||||
return Err(FfiError::Generic {
|
||||
msg: format!(
|
||||
"Invalid HTTP method: {}. Only GET and POST are supported",
|
||||
endpoint.method
|
||||
),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Convert path string to RoutePath by matching against known paths
|
||||
let route_path = match endpoint.path.as_str() {
|
||||
"/v1/mint/quote/bolt11" => cdk::nuts::RoutePath::MintQuoteBolt11,
|
||||
"/v1/mint/bolt11" => cdk::nuts::RoutePath::MintBolt11,
|
||||
"/v1/melt/quote/bolt11" => cdk::nuts::RoutePath::MeltQuoteBolt11,
|
||||
"/v1/melt/bolt11" => cdk::nuts::RoutePath::MeltBolt11,
|
||||
"/v1/swap" => cdk::nuts::RoutePath::Swap,
|
||||
"/v1/checkstate" => cdk::nuts::RoutePath::Checkstate,
|
||||
"/v1/restore" => cdk::nuts::RoutePath::Restore,
|
||||
"/v1/auth/blind/mint" => cdk::nuts::RoutePath::MintBlindAuth,
|
||||
"/v1/mint/quote/bolt12" => cdk::nuts::RoutePath::MintQuoteBolt12,
|
||||
"/v1/mint/bolt12" => cdk::nuts::RoutePath::MintBolt12,
|
||||
"/v1/melt/quote/bolt12" => cdk::nuts::RoutePath::MeltQuoteBolt12,
|
||||
"/v1/melt/bolt12" => cdk::nuts::RoutePath::MeltBolt12,
|
||||
_ => {
|
||||
return Err(FfiError::Generic {
|
||||
msg: format!("Unknown route path: {}", endpoint.path),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(cdk::nuts::ProtectedEndpoint::new(method, route_path))
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Nuts settings (extended to include NUT-04 and NUT-05 settings)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct Nuts {
|
||||
/// NUT04 Settings
|
||||
pub nut04: Nut04Settings,
|
||||
/// NUT05 Settings
|
||||
pub nut05: Nut05Settings,
|
||||
/// NUT07 Settings - Token state check
|
||||
pub nut07_supported: bool,
|
||||
/// NUT08 Settings - Lightning fee return
|
||||
pub nut08_supported: bool,
|
||||
/// NUT09 Settings - Restore signature
|
||||
pub nut09_supported: bool,
|
||||
/// NUT10 Settings - Spending conditions
|
||||
pub nut10_supported: bool,
|
||||
/// NUT11 Settings - Pay to Public Key Hash
|
||||
pub nut11_supported: bool,
|
||||
/// NUT12 Settings - DLEQ proofs
|
||||
pub nut12_supported: bool,
|
||||
/// NUT14 Settings - Hashed Time Locked Contracts
|
||||
pub nut14_supported: bool,
|
||||
/// NUT20 Settings - Web sockets
|
||||
pub nut20_supported: bool,
|
||||
/// NUT21 Settings - Clear authentication
|
||||
pub nut21: Option<ClearAuthSettings>,
|
||||
/// NUT22 Settings - Blind authentication
|
||||
pub nut22: Option<BlindAuthSettings>,
|
||||
/// Supported currency units for minting
|
||||
pub mint_units: Vec<CurrencyUnit>,
|
||||
/// Supported currency units for melting
|
||||
pub melt_units: Vec<CurrencyUnit>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::Nuts> for Nuts {
|
||||
fn from(nuts: cdk::nuts::Nuts) -> Self {
|
||||
let mint_units = nuts
|
||||
.supported_mint_units()
|
||||
.into_iter()
|
||||
.map(|u| u.clone().into())
|
||||
.collect();
|
||||
let melt_units = nuts
|
||||
.supported_melt_units()
|
||||
.into_iter()
|
||||
.map(|u| u.clone().into())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
nut04: nuts.nut04.clone().into(),
|
||||
nut05: nuts.nut05.clone().into(),
|
||||
nut07_supported: nuts.nut07.supported,
|
||||
nut08_supported: nuts.nut08.supported,
|
||||
nut09_supported: nuts.nut09.supported,
|
||||
nut10_supported: nuts.nut10.supported,
|
||||
nut11_supported: nuts.nut11.supported,
|
||||
nut12_supported: nuts.nut12.supported,
|
||||
nut14_supported: nuts.nut14.supported,
|
||||
nut20_supported: nuts.nut20.supported,
|
||||
nut21: nuts.nut21.map(Into::into),
|
||||
nut22: nuts.nut22.map(Into::into),
|
||||
mint_units,
|
||||
melt_units,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Nuts> for cdk::nuts::Nuts {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(n: Nuts) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
nut04: n.nut04.try_into()?,
|
||||
nut05: n.nut05.try_into()?,
|
||||
nut07: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut07_supported,
|
||||
},
|
||||
nut08: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut08_supported,
|
||||
},
|
||||
nut09: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut09_supported,
|
||||
},
|
||||
nut10: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut10_supported,
|
||||
},
|
||||
nut11: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut11_supported,
|
||||
},
|
||||
nut12: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut12_supported,
|
||||
},
|
||||
nut14: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut14_supported,
|
||||
},
|
||||
nut15: Default::default(),
|
||||
nut17: Default::default(),
|
||||
nut19: Default::default(),
|
||||
nut20: cdk::nuts::nut06::SupportedSettings {
|
||||
supported: n.nut20_supported,
|
||||
},
|
||||
nut21: n.nut21.map(|s| s.try_into()).transpose()?,
|
||||
nut22: n.nut22.map(|s| s.try_into()).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Nuts {
|
||||
/// Convert Nuts to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode Nuts from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_nuts(json: String) -> Result<Nuts, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode Nuts to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_nuts(nuts: Nuts) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&nuts)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible MintInfo
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct MintInfo {
|
||||
/// name of the mint and should be recognizable
|
||||
pub name: Option<String>,
|
||||
/// hex pubkey of the mint
|
||||
pub pubkey: Option<String>,
|
||||
/// implementation name and the version running
|
||||
pub version: Option<MintVersion>,
|
||||
/// short description of the mint
|
||||
pub description: Option<String>,
|
||||
/// long description
|
||||
pub description_long: Option<String>,
|
||||
/// Contact info
|
||||
pub contact: Option<Vec<ContactInfo>>,
|
||||
/// shows which NUTs the mint supports
|
||||
pub nuts: Nuts,
|
||||
/// Mint's icon URL
|
||||
pub icon_url: Option<String>,
|
||||
/// Mint's endpoint URLs
|
||||
pub urls: Option<Vec<String>>,
|
||||
/// message of the day that the wallet must display to the user
|
||||
pub motd: Option<String>,
|
||||
/// server unix timestamp
|
||||
pub time: Option<u64>,
|
||||
/// terms of url service of the mint
|
||||
pub tos_url: Option<String>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::MintInfo> for MintInfo {
|
||||
fn from(info: cdk::nuts::MintInfo) -> Self {
|
||||
Self {
|
||||
name: info.name,
|
||||
pubkey: info.pubkey.map(|p| p.to_string()),
|
||||
version: info.version.map(Into::into),
|
||||
description: info.description,
|
||||
description_long: info.description_long,
|
||||
contact: info
|
||||
.contact
|
||||
.map(|contacts| contacts.into_iter().map(Into::into).collect()),
|
||||
nuts: info.nuts.into(),
|
||||
icon_url: info.icon_url,
|
||||
urls: info.urls,
|
||||
motd: info.motd,
|
||||
time: info.time,
|
||||
tos_url: info.tos_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MintInfo> for cdk::nuts::MintInfo {
|
||||
fn from(info: MintInfo) -> Self {
|
||||
// Convert FFI Nuts back to cdk::nuts::Nuts (best-effort)
|
||||
let nuts_cdk: cdk::nuts::Nuts = info.nuts.clone().try_into().unwrap_or_default();
|
||||
Self {
|
||||
name: info.name,
|
||||
pubkey: info.pubkey.and_then(|p| p.parse().ok()),
|
||||
version: info.version.map(Into::into),
|
||||
description: info.description,
|
||||
description_long: info.description_long,
|
||||
contact: info
|
||||
.contact
|
||||
.map(|contacts| contacts.into_iter().map(Into::into).collect()),
|
||||
nuts: nuts_cdk,
|
||||
icon_url: info.icon_url,
|
||||
urls: info.urls,
|
||||
motd: info.motd,
|
||||
time: info.time,
|
||||
tos_url: info.tos_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MintInfo {
|
||||
/// Convert MintInfo to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode MintInfo from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_mint_info(json: String) -> Result<MintInfo, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode MintInfo to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_mint_info(info: MintInfo) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&info)?)
|
||||
}
|
||||
24
crates/cdk-ffi/src/types/mod.rs
Normal file
24
crates/cdk-ffi/src/types/mod.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
//! FFI-compatible types
|
||||
//!
|
||||
//! This module contains all the FFI types used by the UniFFI bindings.
|
||||
//! Types are organized into logical submodules for better maintainability.
|
||||
|
||||
// Module declarations
|
||||
pub mod amount;
|
||||
pub mod keys;
|
||||
pub mod mint;
|
||||
pub mod proof;
|
||||
pub mod quote;
|
||||
pub mod subscription;
|
||||
pub mod transaction;
|
||||
pub mod wallet;
|
||||
|
||||
// Re-export all types for convenient access
|
||||
pub use amount::*;
|
||||
pub use keys::*;
|
||||
pub use mint::*;
|
||||
pub use proof::*;
|
||||
pub use quote::*;
|
||||
pub use subscription::*;
|
||||
pub use transaction::*;
|
||||
pub use wallet::*;
|
||||
509
crates/cdk-ffi/src/types/proof.rs
Normal file
509
crates/cdk-ffi/src/types/proof.rs
Normal file
@@ -0,0 +1,509 @@
|
||||
//! Proof-related FFI types
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use cdk::nuts::State as CdkState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::amount::{Amount, CurrencyUnit};
|
||||
use super::mint::MintUrl;
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible Proof state
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum ProofState {
|
||||
Unspent,
|
||||
Pending,
|
||||
Spent,
|
||||
Reserved,
|
||||
PendingSpent,
|
||||
}
|
||||
|
||||
impl From<CdkState> for ProofState {
|
||||
fn from(state: CdkState) -> Self {
|
||||
match state {
|
||||
CdkState::Unspent => ProofState::Unspent,
|
||||
CdkState::Pending => ProofState::Pending,
|
||||
CdkState::Spent => ProofState::Spent,
|
||||
CdkState::Reserved => ProofState::Reserved,
|
||||
CdkState::PendingSpent => ProofState::PendingSpent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProofState> for CdkState {
|
||||
fn from(state: ProofState) -> Self {
|
||||
match state {
|
||||
ProofState::Unspent => CdkState::Unspent,
|
||||
ProofState::Pending => CdkState::Pending,
|
||||
ProofState::Spent => CdkState::Spent,
|
||||
ProofState::Reserved => CdkState::Reserved,
|
||||
ProofState::PendingSpent => CdkState::PendingSpent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Proof
|
||||
#[derive(Debug, uniffi::Object)]
|
||||
pub struct Proof {
|
||||
pub(crate) inner: cdk::nuts::Proof,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::Proof> for Proof {
|
||||
fn from(proof: cdk::nuts::Proof) -> Self {
|
||||
Self { inner: proof }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Proof> for cdk::nuts::Proof {
|
||||
fn from(proof: Proof) -> Self {
|
||||
proof.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Proof {
|
||||
/// Get the amount
|
||||
pub fn amount(&self) -> Amount {
|
||||
self.inner.amount.into()
|
||||
}
|
||||
|
||||
/// Get the secret as string
|
||||
pub fn secret(&self) -> String {
|
||||
self.inner.secret.to_string()
|
||||
}
|
||||
|
||||
/// Get the unblinded signature (C) as string
|
||||
pub fn c(&self) -> String {
|
||||
self.inner.c.to_string()
|
||||
}
|
||||
|
||||
/// Get the keyset ID as string
|
||||
pub fn keyset_id(&self) -> String {
|
||||
self.inner.keyset_id.to_string()
|
||||
}
|
||||
|
||||
/// Get the witness
|
||||
pub fn witness(&self) -> Option<Witness> {
|
||||
self.inner.witness.as_ref().map(|w| w.clone().into())
|
||||
}
|
||||
|
||||
/// Check if proof is active with given keyset IDs
|
||||
pub fn is_active(&self, active_keyset_ids: Vec<String>) -> bool {
|
||||
use cdk::nuts::Id;
|
||||
let ids: Vec<Id> = active_keyset_ids
|
||||
.into_iter()
|
||||
.filter_map(|id| Id::from_str(&id).ok())
|
||||
.collect();
|
||||
self.inner.is_active(&ids)
|
||||
}
|
||||
|
||||
/// Get the Y value (hash_to_curve of secret)
|
||||
pub fn y(&self) -> Result<String, FfiError> {
|
||||
Ok(self.inner.y()?.to_string())
|
||||
}
|
||||
|
||||
/// Get the DLEQ proof if present
|
||||
pub fn dleq(&self) -> Option<ProofDleq> {
|
||||
self.inner.dleq.as_ref().map(|d| d.clone().into())
|
||||
}
|
||||
|
||||
/// Check if proof has DLEQ proof
|
||||
pub fn has_dleq(&self) -> bool {
|
||||
self.inner.dleq.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Proofs (vector of Proof)
|
||||
pub type Proofs = Vec<std::sync::Arc<Proof>>;
|
||||
|
||||
/// FFI-compatible DLEQ proof for proofs
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct ProofDleq {
|
||||
/// e value (hex-encoded SecretKey)
|
||||
pub e: String,
|
||||
/// s value (hex-encoded SecretKey)
|
||||
pub s: String,
|
||||
/// r value - blinding factor (hex-encoded SecretKey)
|
||||
pub r: String,
|
||||
}
|
||||
|
||||
/// FFI-compatible DLEQ proof for blind signatures
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct BlindSignatureDleq {
|
||||
/// e value (hex-encoded SecretKey)
|
||||
pub e: String,
|
||||
/// s value (hex-encoded SecretKey)
|
||||
pub s: String,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::ProofDleq> for ProofDleq {
|
||||
fn from(dleq: cdk::nuts::ProofDleq) -> Self {
|
||||
Self {
|
||||
e: dleq.e.to_secret_hex(),
|
||||
s: dleq.s.to_secret_hex(),
|
||||
r: dleq.r.to_secret_hex(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProofDleq> for cdk::nuts::ProofDleq {
|
||||
fn from(dleq: ProofDleq) -> Self {
|
||||
Self {
|
||||
e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
|
||||
s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
|
||||
r: cdk::nuts::SecretKey::from_hex(&dleq.r).expect("Invalid r hex"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
|
||||
fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
|
||||
Self {
|
||||
e: dleq.e.to_secret_hex(),
|
||||
s: dleq.s.to_secret_hex(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
|
||||
fn from(dleq: BlindSignatureDleq) -> Self {
|
||||
Self {
|
||||
e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
|
||||
s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper functions for Proofs
|
||||
pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
|
||||
let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
|
||||
use cdk::nuts::ProofsMethods;
|
||||
Ok(cdk_proofs.total_amount()?.into())
|
||||
}
|
||||
|
||||
/// FFI-compatible Conditions (for spending conditions)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct Conditions {
|
||||
/// Unix locktime after which refund keys can be used
|
||||
pub locktime: Option<u64>,
|
||||
/// Additional Public keys (as hex strings)
|
||||
pub pubkeys: Vec<String>,
|
||||
/// Refund keys (as hex strings)
|
||||
pub refund_keys: Vec<String>,
|
||||
/// Number of signatures required (default 1)
|
||||
pub num_sigs: Option<u64>,
|
||||
/// Signature flag (0 = SigInputs, 1 = SigAll)
|
||||
pub sig_flag: u8,
|
||||
/// Number of refund signatures required (default 1)
|
||||
pub num_sigs_refund: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut11::Conditions> for Conditions {
|
||||
fn from(conditions: cdk::nuts::nut11::Conditions) -> Self {
|
||||
Self {
|
||||
locktime: conditions.locktime,
|
||||
pubkeys: conditions
|
||||
.pubkeys
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect(),
|
||||
refund_keys: conditions
|
||||
.refund_keys
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect(),
|
||||
num_sigs: conditions.num_sigs,
|
||||
sig_flag: match conditions.sig_flag {
|
||||
cdk::nuts::nut11::SigFlag::SigInputs => 0,
|
||||
cdk::nuts::nut11::SigFlag::SigAll => 1,
|
||||
},
|
||||
num_sigs_refund: conditions.num_sigs_refund,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
|
||||
let pubkeys = if conditions.pubkeys.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
conditions
|
||||
.pubkeys
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
|
||||
msg: format!("Invalid pubkey: {}", e),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
)
|
||||
};
|
||||
|
||||
let refund_keys = if conditions.refund_keys.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
conditions
|
||||
.refund_keys
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
|
||||
msg: format!("Invalid refund key: {}", e),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
)
|
||||
};
|
||||
|
||||
let sig_flag = match conditions.sig_flag {
|
||||
0 => cdk::nuts::nut11::SigFlag::SigInputs,
|
||||
1 => cdk::nuts::nut11::SigFlag::SigAll,
|
||||
_ => {
|
||||
return Err(FfiError::Generic {
|
||||
msg: "Invalid sig_flag value".to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
locktime: conditions.locktime,
|
||||
pubkeys,
|
||||
refund_keys,
|
||||
num_sigs: conditions.num_sigs,
|
||||
sig_flag,
|
||||
num_sigs_refund: conditions.num_sigs_refund,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Conditions {
|
||||
/// Convert Conditions to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode Conditions from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode Conditions to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&conditions)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible Witness
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum Witness {
|
||||
/// P2PK Witness
|
||||
P2PK {
|
||||
/// Signatures
|
||||
signatures: Vec<String>,
|
||||
},
|
||||
/// HTLC Witness
|
||||
HTLC {
|
||||
/// Preimage
|
||||
preimage: String,
|
||||
/// Optional signatures
|
||||
signatures: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::Witness> for Witness {
|
||||
fn from(witness: cdk::nuts::Witness) -> Self {
|
||||
match witness {
|
||||
cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
|
||||
signatures: p2pk.signatures,
|
||||
},
|
||||
cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
|
||||
preimage: htlc.preimage,
|
||||
signatures: htlc.signatures,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Witness> for cdk::nuts::Witness {
|
||||
fn from(witness: Witness) -> Self {
|
||||
match witness {
|
||||
Witness::P2PK { signatures } => {
|
||||
Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
|
||||
}
|
||||
Witness::HTLC {
|
||||
preimage,
|
||||
signatures,
|
||||
} => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
|
||||
preimage,
|
||||
signatures,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible SpendingConditions
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum SpendingConditions {
|
||||
/// P2PK (Pay to Public Key) conditions
|
||||
P2PK {
|
||||
/// The public key (as hex string)
|
||||
pubkey: String,
|
||||
/// Additional conditions
|
||||
conditions: Option<Conditions>,
|
||||
},
|
||||
/// HTLC (Hash Time Locked Contract) conditions
|
||||
HTLC {
|
||||
/// Hash of the preimage (as hex string)
|
||||
hash: String,
|
||||
/// Additional conditions
|
||||
conditions: Option<Conditions>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
|
||||
fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
|
||||
match spending_conditions {
|
||||
cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
|
||||
pubkey: data.to_string(),
|
||||
conditions: conditions.map(Into::into),
|
||||
},
|
||||
cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
|
||||
hash: data.to_string(),
|
||||
conditions: conditions.map(Into::into),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
|
||||
match spending_conditions {
|
||||
SpendingConditions::P2PK { pubkey, conditions } => {
|
||||
let pubkey = pubkey
|
||||
.parse()
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey {
|
||||
msg: format!("Invalid pubkey: {}", e),
|
||||
})?;
|
||||
let conditions = conditions.map(|c| c.try_into()).transpose()?;
|
||||
Ok(Self::P2PKConditions {
|
||||
data: pubkey,
|
||||
conditions,
|
||||
})
|
||||
}
|
||||
SpendingConditions::HTLC { hash, conditions } => {
|
||||
let hash = hash
|
||||
.parse()
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey {
|
||||
msg: format!("Invalid hash: {}", e),
|
||||
})?;
|
||||
let conditions = conditions.map(|c| c.try_into()).transpose()?;
|
||||
Ok(Self::HTLCConditions {
|
||||
data: hash,
|
||||
conditions,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible ProofInfo
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct ProofInfo {
|
||||
/// Proof
|
||||
pub proof: std::sync::Arc<Proof>,
|
||||
/// Y value (hash_to_curve of secret)
|
||||
pub y: super::keys::PublicKey,
|
||||
/// Mint URL
|
||||
pub mint_url: MintUrl,
|
||||
/// Proof state
|
||||
pub state: ProofState,
|
||||
/// Proof Spending Conditions
|
||||
pub spending_condition: Option<SpendingConditions>,
|
||||
/// Currency unit
|
||||
pub unit: CurrencyUnit,
|
||||
}
|
||||
|
||||
impl From<cdk::types::ProofInfo> for ProofInfo {
|
||||
fn from(info: cdk::types::ProofInfo) -> Self {
|
||||
Self {
|
||||
proof: std::sync::Arc::new(info.proof.into()),
|
||||
y: info.y.into(),
|
||||
mint_url: info.mint_url.into(),
|
||||
state: info.state.into(),
|
||||
spending_condition: info.spending_condition.map(Into::into),
|
||||
unit: info.unit.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode ProofInfo from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
|
||||
let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
|
||||
Ok(info.into())
|
||||
}
|
||||
|
||||
/// Encode ProofInfo to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
|
||||
// Convert to cdk::types::ProofInfo for serialization
|
||||
let cdk_info = cdk::types::ProofInfo {
|
||||
proof: info.proof.inner.clone(),
|
||||
y: info.y.try_into()?,
|
||||
mint_url: info.mint_url.try_into()?,
|
||||
state: info.state.into(),
|
||||
spending_condition: info.spending_condition.and_then(|c| c.try_into().ok()),
|
||||
unit: info.unit.into(),
|
||||
};
|
||||
Ok(serde_json::to_string(&cdk_info)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible ProofStateUpdate
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct ProofStateUpdate {
|
||||
/// Y value (hash_to_curve of secret)
|
||||
pub y: String,
|
||||
/// Current state
|
||||
pub state: ProofState,
|
||||
/// Optional witness data
|
||||
pub witness: Option<String>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
|
||||
fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
|
||||
Self {
|
||||
y: proof_state.y.to_string(),
|
||||
state: proof_state.state.into(),
|
||||
witness: proof_state.witness.map(|w| format!("{:?}", w)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProofStateUpdate {
|
||||
/// Convert ProofStateUpdate to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode ProofStateUpdate from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode ProofStateUpdate to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&update)?)
|
||||
}
|
||||
430
crates/cdk-ffi/src/types/quote.rs
Normal file
430
crates/cdk-ffi/src/types/quote.rs
Normal file
@@ -0,0 +1,430 @@
|
||||
//! Quote-related FFI types
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::amount::{Amount, CurrencyUnit};
|
||||
use super::mint::MintUrl;
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible MintQuote
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct MintQuote {
|
||||
/// Quote ID
|
||||
pub id: String,
|
||||
/// Quote amount
|
||||
pub amount: Option<Amount>,
|
||||
/// Currency unit
|
||||
pub unit: CurrencyUnit,
|
||||
/// Payment request
|
||||
pub request: String,
|
||||
/// Quote state
|
||||
pub state: QuoteState,
|
||||
/// Expiry timestamp
|
||||
pub expiry: u64,
|
||||
/// Mint URL
|
||||
pub mint_url: MintUrl,
|
||||
/// Amount issued
|
||||
pub amount_issued: Amount,
|
||||
/// Amount paid
|
||||
pub amount_paid: Amount,
|
||||
/// Payment method
|
||||
pub payment_method: PaymentMethod,
|
||||
/// Secret key (optional, hex-encoded)
|
||||
pub secret_key: Option<String>,
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::MintQuote> for MintQuote {
|
||||
fn from(quote: cdk::wallet::MintQuote) -> Self {
|
||||
Self {
|
||||
id: quote.id.clone(),
|
||||
amount: quote.amount.map(Into::into),
|
||||
unit: quote.unit.clone().into(),
|
||||
request: quote.request.clone(),
|
||||
state: quote.state.into(),
|
||||
expiry: quote.expiry,
|
||||
mint_url: quote.mint_url.clone().into(),
|
||||
amount_issued: quote.amount_issued.into(),
|
||||
amount_paid: quote.amount_paid.into(),
|
||||
payment_method: quote.payment_method.into(),
|
||||
secret_key: quote.secret_key.map(|sk| sk.to_secret_hex()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MintQuote> for cdk::wallet::MintQuote {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
|
||||
let secret_key = quote
|
||||
.secret_key
|
||||
.map(|hex| cdk::nuts::SecretKey::from_hex(&hex))
|
||||
.transpose()
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
|
||||
|
||||
Ok(Self {
|
||||
id: quote.id,
|
||||
amount: quote.amount.map(Into::into),
|
||||
unit: quote.unit.into(),
|
||||
request: quote.request,
|
||||
state: quote.state.into(),
|
||||
expiry: quote.expiry,
|
||||
mint_url: quote.mint_url.try_into()?,
|
||||
amount_issued: quote.amount_issued.into(),
|
||||
amount_paid: quote.amount_paid.into(),
|
||||
payment_method: quote.payment_method.into(),
|
||||
secret_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MintQuote {
|
||||
/// Get total amount (amount + fees)
|
||||
pub fn total_amount(&self) -> Amount {
|
||||
if let Some(amount) = self.amount {
|
||||
Amount::new(amount.value + self.amount_paid.value - self.amount_issued.value)
|
||||
} else {
|
||||
Amount::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if quote is expired
|
||||
pub fn is_expired(&self, current_time: u64) -> bool {
|
||||
current_time > self.expiry
|
||||
}
|
||||
|
||||
/// Get amount that can be minted
|
||||
pub fn amount_mintable(&self) -> Amount {
|
||||
Amount::new(self.amount_paid.value - self.amount_issued.value)
|
||||
}
|
||||
|
||||
/// Convert MintQuote to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode MintQuote from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_mint_quote(json: String) -> Result<MintQuote, FfiError> {
|
||||
let quote: cdk::wallet::MintQuote = serde_json::from_str(&json)?;
|
||||
Ok(quote.into())
|
||||
}
|
||||
|
||||
/// Encode MintQuote to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_mint_quote(quote: MintQuote) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string("e)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible MintQuoteBolt11Response
|
||||
#[derive(Debug, uniffi::Object)]
|
||||
pub struct MintQuoteBolt11Response {
|
||||
/// Quote ID
|
||||
pub quote: String,
|
||||
/// Request string
|
||||
pub request: String,
|
||||
/// State of the quote
|
||||
pub state: QuoteState,
|
||||
/// Expiry timestamp (optional)
|
||||
pub expiry: Option<u64>,
|
||||
/// Amount (optional)
|
||||
pub amount: Option<Amount>,
|
||||
/// Unit (optional)
|
||||
pub unit: Option<CurrencyUnit>,
|
||||
/// Pubkey (optional)
|
||||
pub pubkey: Option<String>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::MintQuoteBolt11Response<String>> for MintQuoteBolt11Response {
|
||||
fn from(response: cdk::nuts::MintQuoteBolt11Response<String>) -> Self {
|
||||
Self {
|
||||
quote: response.quote,
|
||||
request: response.request,
|
||||
state: response.state.into(),
|
||||
expiry: response.expiry,
|
||||
amount: response.amount.map(Into::into),
|
||||
unit: response.unit.map(Into::into),
|
||||
pubkey: response.pubkey.map(|p| p.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl MintQuoteBolt11Response {
|
||||
/// Get quote ID
|
||||
pub fn quote(&self) -> String {
|
||||
self.quote.clone()
|
||||
}
|
||||
|
||||
/// Get request string
|
||||
pub fn request(&self) -> String {
|
||||
self.request.clone()
|
||||
}
|
||||
|
||||
/// Get state
|
||||
pub fn state(&self) -> QuoteState {
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
/// Get expiry
|
||||
pub fn expiry(&self) -> Option<u64> {
|
||||
self.expiry
|
||||
}
|
||||
|
||||
/// Get amount
|
||||
pub fn amount(&self) -> Option<Amount> {
|
||||
self.amount
|
||||
}
|
||||
|
||||
/// Get unit
|
||||
pub fn unit(&self) -> Option<CurrencyUnit> {
|
||||
self.unit.clone()
|
||||
}
|
||||
|
||||
/// Get pubkey
|
||||
pub fn pubkey(&self) -> Option<String> {
|
||||
self.pubkey.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible MeltQuoteBolt11Response
|
||||
#[derive(Debug, uniffi::Object)]
|
||||
pub struct MeltQuoteBolt11Response {
|
||||
/// Quote ID
|
||||
pub quote: String,
|
||||
/// Amount
|
||||
pub amount: Amount,
|
||||
/// Fee reserve
|
||||
pub fee_reserve: Amount,
|
||||
/// State of the quote
|
||||
pub state: QuoteState,
|
||||
/// Expiry timestamp
|
||||
pub expiry: u64,
|
||||
/// Payment preimage (optional)
|
||||
pub payment_preimage: Option<String>,
|
||||
/// Request string (optional)
|
||||
pub request: Option<String>,
|
||||
/// Unit (optional)
|
||||
pub unit: Option<CurrencyUnit>,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::MeltQuoteBolt11Response<String>> for MeltQuoteBolt11Response {
|
||||
fn from(response: cdk::nuts::MeltQuoteBolt11Response<String>) -> Self {
|
||||
Self {
|
||||
quote: response.quote,
|
||||
amount: response.amount.into(),
|
||||
fee_reserve: response.fee_reserve.into(),
|
||||
state: response.state.into(),
|
||||
expiry: response.expiry,
|
||||
payment_preimage: response.payment_preimage,
|
||||
request: response.request,
|
||||
unit: response.unit.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl MeltQuoteBolt11Response {
|
||||
/// Get quote ID
|
||||
pub fn quote(&self) -> String {
|
||||
self.quote.clone()
|
||||
}
|
||||
|
||||
/// Get amount
|
||||
pub fn amount(&self) -> Amount {
|
||||
self.amount
|
||||
}
|
||||
|
||||
/// Get fee reserve
|
||||
pub fn fee_reserve(&self) -> Amount {
|
||||
self.fee_reserve
|
||||
}
|
||||
|
||||
/// Get state
|
||||
pub fn state(&self) -> QuoteState {
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
/// Get expiry
|
||||
pub fn expiry(&self) -> u64 {
|
||||
self.expiry
|
||||
}
|
||||
|
||||
/// Get payment preimage
|
||||
pub fn payment_preimage(&self) -> Option<String> {
|
||||
self.payment_preimage.clone()
|
||||
}
|
||||
|
||||
/// Get request
|
||||
pub fn request(&self) -> Option<String> {
|
||||
self.request.clone()
|
||||
}
|
||||
|
||||
/// Get unit
|
||||
pub fn unit(&self) -> Option<CurrencyUnit> {
|
||||
self.unit.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible PaymentMethod
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum PaymentMethod {
|
||||
/// Bolt11 payment type
|
||||
Bolt11,
|
||||
/// Bolt12 payment type
|
||||
Bolt12,
|
||||
/// Custom payment type
|
||||
Custom { method: String },
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::PaymentMethod> for PaymentMethod {
|
||||
fn from(method: cdk::nuts::PaymentMethod) -> Self {
|
||||
match method {
|
||||
cdk::nuts::PaymentMethod::Bolt11 => Self::Bolt11,
|
||||
cdk::nuts::PaymentMethod::Bolt12 => Self::Bolt12,
|
||||
cdk::nuts::PaymentMethod::Custom(s) => Self::Custom { method: s },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaymentMethod> for cdk::nuts::PaymentMethod {
|
||||
fn from(method: PaymentMethod) -> Self {
|
||||
match method {
|
||||
PaymentMethod::Bolt11 => Self::Bolt11,
|
||||
PaymentMethod::Bolt12 => Self::Bolt12,
|
||||
PaymentMethod::Custom { method } => Self::Custom(method),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible MeltQuote
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct MeltQuote {
|
||||
/// Quote ID
|
||||
pub id: String,
|
||||
/// Quote amount
|
||||
pub amount: Amount,
|
||||
/// Currency unit
|
||||
pub unit: CurrencyUnit,
|
||||
/// Payment request
|
||||
pub request: String,
|
||||
/// Fee reserve
|
||||
pub fee_reserve: Amount,
|
||||
/// Quote state
|
||||
pub state: QuoteState,
|
||||
/// Expiry timestamp
|
||||
pub expiry: u64,
|
||||
/// Payment preimage
|
||||
pub payment_preimage: Option<String>,
|
||||
/// Payment method
|
||||
pub payment_method: PaymentMethod,
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::MeltQuote> for MeltQuote {
|
||||
fn from(quote: cdk::wallet::MeltQuote) -> Self {
|
||||
Self {
|
||||
id: quote.id.clone(),
|
||||
amount: quote.amount.into(),
|
||||
unit: quote.unit.clone().into(),
|
||||
request: quote.request.clone(),
|
||||
fee_reserve: quote.fee_reserve.into(),
|
||||
state: quote.state.into(),
|
||||
expiry: quote.expiry,
|
||||
payment_preimage: quote.payment_preimage.clone(),
|
||||
payment_method: quote.payment_method.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MeltQuote> for cdk::wallet::MeltQuote {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(quote: MeltQuote) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
id: quote.id,
|
||||
amount: quote.amount.into(),
|
||||
unit: quote.unit.into(),
|
||||
request: quote.request,
|
||||
fee_reserve: quote.fee_reserve.into(),
|
||||
state: quote.state.into(),
|
||||
expiry: quote.expiry,
|
||||
payment_preimage: quote.payment_preimage,
|
||||
payment_method: quote.payment_method.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MeltQuote {
|
||||
/// Convert MeltQuote to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode MeltQuote from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_melt_quote(json: String) -> Result<MeltQuote, FfiError> {
|
||||
let quote: cdk::wallet::MeltQuote = serde_json::from_str(&json)?;
|
||||
Ok(quote.into())
|
||||
}
|
||||
|
||||
/// Encode MeltQuote to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_melt_quote(quote: MeltQuote) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string("e)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible QuoteState
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum QuoteState {
|
||||
Unpaid,
|
||||
Paid,
|
||||
Pending,
|
||||
Issued,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut05::QuoteState> for QuoteState {
|
||||
fn from(state: cdk::nuts::nut05::QuoteState) -> Self {
|
||||
match state {
|
||||
cdk::nuts::nut05::QuoteState::Unpaid => QuoteState::Unpaid,
|
||||
cdk::nuts::nut05::QuoteState::Paid => QuoteState::Paid,
|
||||
cdk::nuts::nut05::QuoteState::Pending => QuoteState::Pending,
|
||||
cdk::nuts::nut05::QuoteState::Unknown => QuoteState::Unpaid,
|
||||
cdk::nuts::nut05::QuoteState::Failed => QuoteState::Unpaid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QuoteState> for cdk::nuts::nut05::QuoteState {
|
||||
fn from(state: QuoteState) -> Self {
|
||||
match state {
|
||||
QuoteState::Unpaid => cdk::nuts::nut05::QuoteState::Unpaid,
|
||||
QuoteState::Paid => cdk::nuts::nut05::QuoteState::Paid,
|
||||
QuoteState::Pending => cdk::nuts::nut05::QuoteState::Pending,
|
||||
QuoteState::Issued => cdk::nuts::nut05::QuoteState::Paid, // Map issued to paid for melt quotes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::MintQuoteState> for QuoteState {
|
||||
fn from(state: cdk::nuts::MintQuoteState) -> Self {
|
||||
match state {
|
||||
cdk::nuts::MintQuoteState::Unpaid => QuoteState::Unpaid,
|
||||
cdk::nuts::MintQuoteState::Paid => QuoteState::Paid,
|
||||
cdk::nuts::MintQuoteState::Issued => QuoteState::Issued,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QuoteState> for cdk::nuts::MintQuoteState {
|
||||
fn from(state: QuoteState) -> Self {
|
||||
match state {
|
||||
QuoteState::Unpaid => cdk::nuts::MintQuoteState::Unpaid,
|
||||
QuoteState::Paid => cdk::nuts::MintQuoteState::Paid,
|
||||
QuoteState::Issued => cdk::nuts::MintQuoteState::Issued,
|
||||
QuoteState::Pending => cdk::nuts::MintQuoteState::Paid, // Map pending to paid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: MeltQuoteState is the same as nut05::QuoteState, so we don't need a separate impl
|
||||
183
crates/cdk-ffi/src/types/subscription.rs
Normal file
183
crates/cdk-ffi/src/types/subscription.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
//! Subscription-related FFI types
|
||||
|
||||
use cdk::pub_sub::SubId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::proof::ProofStateUpdate;
|
||||
use super::quote::{MeltQuoteBolt11Response, MintQuoteBolt11Response};
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible SubscriptionKind
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum SubscriptionKind {
|
||||
/// Bolt 11 Melt Quote
|
||||
Bolt11MeltQuote,
|
||||
/// Bolt 11 Mint Quote
|
||||
Bolt11MintQuote,
|
||||
/// Bolt 12 Mint Quote
|
||||
Bolt12MintQuote,
|
||||
/// Proof State
|
||||
ProofState,
|
||||
}
|
||||
|
||||
impl From<SubscriptionKind> for cdk::nuts::nut17::Kind {
|
||||
fn from(kind: SubscriptionKind) -> Self {
|
||||
match kind {
|
||||
SubscriptionKind::Bolt11MeltQuote => cdk::nuts::nut17::Kind::Bolt11MeltQuote,
|
||||
SubscriptionKind::Bolt11MintQuote => cdk::nuts::nut17::Kind::Bolt11MintQuote,
|
||||
SubscriptionKind::Bolt12MintQuote => cdk::nuts::nut17::Kind::Bolt12MintQuote,
|
||||
SubscriptionKind::ProofState => cdk::nuts::nut17::Kind::ProofState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::nut17::Kind> for SubscriptionKind {
|
||||
fn from(kind: cdk::nuts::nut17::Kind) -> Self {
|
||||
match kind {
|
||||
cdk::nuts::nut17::Kind::Bolt11MeltQuote => SubscriptionKind::Bolt11MeltQuote,
|
||||
cdk::nuts::nut17::Kind::Bolt11MintQuote => SubscriptionKind::Bolt11MintQuote,
|
||||
cdk::nuts::nut17::Kind::Bolt12MintQuote => SubscriptionKind::Bolt12MintQuote,
|
||||
cdk::nuts::nut17::Kind::ProofState => SubscriptionKind::ProofState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible SubscribeParams
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct SubscribeParams {
|
||||
/// Subscription kind
|
||||
pub kind: SubscriptionKind,
|
||||
/// Filters
|
||||
pub filters: Vec<String>,
|
||||
/// Subscription ID (optional, will be generated if not provided)
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<SubscribeParams> for cdk::nuts::nut17::Params<cdk::pub_sub::SubId> {
|
||||
fn from(params: SubscribeParams) -> Self {
|
||||
let sub_id = params
|
||||
.id
|
||||
.map(|id| SubId::from(id.as_str()))
|
||||
.unwrap_or_else(|| {
|
||||
// Generate a random ID
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
SubId::from(uuid.to_string().as_str())
|
||||
});
|
||||
|
||||
cdk::nuts::nut17::Params {
|
||||
kind: params.kind.into(),
|
||||
filters: params.filters,
|
||||
id: sub_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubscribeParams {
|
||||
/// Convert SubscribeParams to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode SubscribeParams from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_subscribe_params(json: String) -> Result<SubscribeParams, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode SubscribeParams to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_subscribe_params(params: SubscribeParams) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(¶ms)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible ActiveSubscription
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct ActiveSubscription {
|
||||
inner: std::sync::Arc<tokio::sync::Mutex<cdk::wallet::subscription::ActiveSubscription>>,
|
||||
pub sub_id: String,
|
||||
}
|
||||
|
||||
impl ActiveSubscription {
|
||||
pub(crate) fn new(
|
||||
inner: cdk::wallet::subscription::ActiveSubscription,
|
||||
sub_id: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: std::sync::Arc::new(tokio::sync::Mutex::new(inner)),
|
||||
sub_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
impl ActiveSubscription {
|
||||
/// Get the subscription ID
|
||||
pub fn id(&self) -> String {
|
||||
self.sub_id.clone()
|
||||
}
|
||||
|
||||
/// Receive the next notification
|
||||
pub async fn recv(&self) -> Result<NotificationPayload, FfiError> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
guard
|
||||
.recv()
|
||||
.await
|
||||
.ok_or(FfiError::Generic {
|
||||
msg: "Subscription closed".to_string(),
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
/// Try to receive a notification without blocking
|
||||
pub async fn try_recv(&self) -> Result<Option<NotificationPayload>, FfiError> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
guard
|
||||
.try_recv()
|
||||
.map(|opt| opt.map(Into::into))
|
||||
.map_err(|e| FfiError::Generic {
|
||||
msg: format!("Failed to receive notification: {}", e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible NotificationPayload
|
||||
#[derive(Debug, Clone, uniffi::Enum)]
|
||||
pub enum NotificationPayload {
|
||||
/// Proof state update
|
||||
ProofState { proof_states: Vec<ProofStateUpdate> },
|
||||
/// Mint quote update
|
||||
MintQuoteUpdate {
|
||||
quote: std::sync::Arc<MintQuoteBolt11Response>,
|
||||
},
|
||||
/// Melt quote update
|
||||
MeltQuoteUpdate {
|
||||
quote: std::sync::Arc<MeltQuoteBolt11Response>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::NotificationPayload<String>> for NotificationPayload {
|
||||
fn from(payload: cdk::nuts::NotificationPayload<String>) -> Self {
|
||||
match payload {
|
||||
cdk::nuts::NotificationPayload::ProofState(states) => NotificationPayload::ProofState {
|
||||
proof_states: vec![states.into()],
|
||||
},
|
||||
cdk::nuts::NotificationPayload::MintQuoteBolt11Response(quote_resp) => {
|
||||
NotificationPayload::MintQuoteUpdate {
|
||||
quote: std::sync::Arc::new(quote_resp.into()),
|
||||
}
|
||||
}
|
||||
cdk::nuts::NotificationPayload::MeltQuoteBolt11Response(quote_resp) => {
|
||||
NotificationPayload::MeltQuoteUpdate {
|
||||
quote: std::sync::Arc::new(quote_resp.into()),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// For now, handle other notification types as empty ProofState
|
||||
NotificationPayload::ProofState {
|
||||
proof_states: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
247
crates/cdk-ffi/src/types/transaction.rs
Normal file
247
crates/cdk-ffi/src/types/transaction.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
//! Transaction-related FFI types
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::amount::{Amount, CurrencyUnit};
|
||||
use super::keys::PublicKey;
|
||||
use super::mint::MintUrl;
|
||||
use super::proof::Proofs;
|
||||
use crate::error::FfiError;
|
||||
|
||||
/// FFI-compatible Transaction
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct Transaction {
|
||||
/// Transaction ID
|
||||
pub id: TransactionId,
|
||||
/// Mint URL
|
||||
pub mint_url: MintUrl,
|
||||
/// Transaction direction
|
||||
pub direction: TransactionDirection,
|
||||
/// Amount
|
||||
pub amount: Amount,
|
||||
/// Fee
|
||||
pub fee: Amount,
|
||||
/// Currency Unit
|
||||
pub unit: CurrencyUnit,
|
||||
/// Proof Ys (Y values from proofs)
|
||||
pub ys: Vec<PublicKey>,
|
||||
/// Unix timestamp
|
||||
pub timestamp: u64,
|
||||
/// Memo
|
||||
pub memo: Option<String>,
|
||||
/// User-defined metadata
|
||||
pub metadata: HashMap<String, String>,
|
||||
/// Quote ID if this is a mint or melt transaction
|
||||
pub quote_id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::types::Transaction> for Transaction {
|
||||
fn from(tx: cdk::wallet::types::Transaction) -> Self {
|
||||
Self {
|
||||
id: tx.id().into(),
|
||||
mint_url: tx.mint_url.into(),
|
||||
direction: tx.direction.into(),
|
||||
amount: tx.amount.into(),
|
||||
fee: tx.fee.into(),
|
||||
unit: tx.unit.into(),
|
||||
ys: tx.ys.into_iter().map(Into::into).collect(),
|
||||
timestamp: tx.timestamp,
|
||||
memo: tx.memo,
|
||||
metadata: tx.metadata,
|
||||
quote_id: tx.quote_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert FFI Transaction to CDK Transaction
|
||||
impl TryFrom<Transaction> for cdk::wallet::types::Transaction {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
|
||||
let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, _> =
|
||||
tx.ys.into_iter().map(|pk| pk.try_into()).collect();
|
||||
let cdk_ys = cdk_ys?;
|
||||
|
||||
Ok(Self {
|
||||
mint_url: tx.mint_url.try_into()?,
|
||||
direction: tx.direction.into(),
|
||||
amount: tx.amount.into(),
|
||||
fee: tx.fee.into(),
|
||||
unit: tx.unit.into(),
|
||||
ys: cdk_ys,
|
||||
timestamp: tx.timestamp,
|
||||
memo: tx.memo,
|
||||
metadata: tx.metadata,
|
||||
quote_id: tx.quote_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// Convert Transaction to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode Transaction from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_transaction(json: String) -> Result<Transaction, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode Transaction to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_transaction(transaction: Transaction) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&transaction)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible TransactionDirection
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum TransactionDirection {
|
||||
/// Incoming transaction (i.e., receive or mint)
|
||||
Incoming,
|
||||
/// Outgoing transaction (i.e., send or melt)
|
||||
Outgoing,
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::types::TransactionDirection> for TransactionDirection {
|
||||
fn from(direction: cdk::wallet::types::TransactionDirection) -> Self {
|
||||
match direction {
|
||||
cdk::wallet::types::TransactionDirection::Incoming => TransactionDirection::Incoming,
|
||||
cdk::wallet::types::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionDirection> for cdk::wallet::types::TransactionDirection {
|
||||
fn from(direction: TransactionDirection) -> Self {
|
||||
match direction {
|
||||
TransactionDirection::Incoming => cdk::wallet::types::TransactionDirection::Incoming,
|
||||
TransactionDirection::Outgoing => cdk::wallet::types::TransactionDirection::Outgoing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible TransactionId
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct TransactionId {
|
||||
/// Hex-encoded transaction ID (64 characters)
|
||||
pub hex: String,
|
||||
}
|
||||
|
||||
impl TransactionId {
|
||||
/// Create a new TransactionId from hex string
|
||||
pub fn from_hex(hex: String) -> Result<Self, FfiError> {
|
||||
// Validate hex string length (should be 64 characters for 32 bytes)
|
||||
if hex.len() != 64 {
|
||||
return Err(FfiError::InvalidHex {
|
||||
msg: "Transaction ID hex must be exactly 64 characters (32 bytes)".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Validate hex format
|
||||
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(FfiError::InvalidHex {
|
||||
msg: "Transaction ID hex contains invalid characters".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self { hex })
|
||||
}
|
||||
|
||||
/// Create from proofs
|
||||
pub fn from_proofs(proofs: &Proofs) -> Result<Self, FfiError> {
|
||||
let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
|
||||
let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?;
|
||||
Ok(Self {
|
||||
hex: id.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::types::TransactionId> for TransactionId {
|
||||
fn from(id: cdk::wallet::types::TransactionId) -> Self {
|
||||
Self {
|
||||
hex: id.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
|
||||
cdk::wallet::types::TransactionId::from_hex(&id.hex)
|
||||
.map_err(|e| FfiError::InvalidHex { msg: e.to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible AuthProof
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct AuthProof {
|
||||
/// Keyset ID
|
||||
pub keyset_id: String,
|
||||
/// Secret message
|
||||
pub secret: String,
|
||||
/// Unblinded signature (C)
|
||||
pub c: String,
|
||||
/// Y value (hash_to_curve of secret)
|
||||
pub y: String,
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::AuthProof> for AuthProof {
|
||||
fn from(auth_proof: cdk::nuts::AuthProof) -> Self {
|
||||
Self {
|
||||
keyset_id: auth_proof.keyset_id.to_string(),
|
||||
secret: auth_proof.secret.to_string(),
|
||||
c: auth_proof.c.to_string(),
|
||||
y: auth_proof
|
||||
.y()
|
||||
.map(|y| y.to_string())
|
||||
.unwrap_or_else(|_| "".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
|
||||
type Error = FfiError;
|
||||
|
||||
fn try_from(auth_proof: AuthProof) -> Result<Self, Self::Error> {
|
||||
use std::str::FromStr;
|
||||
Ok(Self {
|
||||
keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
|
||||
.map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
|
||||
secret: {
|
||||
use std::str::FromStr;
|
||||
cdk::secret::Secret::from_str(&auth_proof.secret)
|
||||
.map_err(|e| FfiError::Serialization { msg: e.to_string() })?
|
||||
},
|
||||
c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
|
||||
.map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
|
||||
dleq: None, // FFI doesn't expose DLEQ proofs for simplicity
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthProof {
|
||||
/// Convert AuthProof to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode AuthProof from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_auth_proof(json: String) -> Result<AuthProof, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode AuthProof to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_auth_proof(proof: AuthProof) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&proof)?)
|
||||
}
|
||||
471
crates/cdk-ffi/src/types/wallet.rs
Normal file
471
crates/cdk-ffi/src/types/wallet.rs
Normal file
@@ -0,0 +1,471 @@
|
||||
//! Wallet-related FFI types
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::amount::{Amount, SplitTarget};
|
||||
use super::proof::{Proofs, SpendingConditions};
|
||||
use crate::error::FfiError;
|
||||
use crate::token::Token;
|
||||
|
||||
/// FFI-compatible SendMemo
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct SendMemo {
|
||||
/// Memo text
|
||||
pub memo: String,
|
||||
/// Include memo in token
|
||||
pub include_memo: bool,
|
||||
}
|
||||
|
||||
impl From<SendMemo> for cdk::wallet::SendMemo {
|
||||
fn from(memo: SendMemo) -> Self {
|
||||
cdk::wallet::SendMemo {
|
||||
memo: memo.memo,
|
||||
include_memo: memo.include_memo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::SendMemo> for SendMemo {
|
||||
fn from(memo: cdk::wallet::SendMemo) -> Self {
|
||||
Self {
|
||||
memo: memo.memo,
|
||||
include_memo: memo.include_memo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SendMemo {
|
||||
/// Convert SendMemo to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode SendMemo from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_send_memo(json: String) -> Result<SendMemo, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode SendMemo to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_send_memo(memo: SendMemo) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&memo)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible SendKind
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum SendKind {
|
||||
/// Allow online swap before send if wallet does not have exact amount
|
||||
OnlineExact,
|
||||
/// Prefer offline send if difference is less than tolerance
|
||||
OnlineTolerance { tolerance: Amount },
|
||||
/// Wallet cannot do an online swap and selected proof must be exactly send amount
|
||||
OfflineExact,
|
||||
/// Wallet must remain offline but can over pay if below tolerance
|
||||
OfflineTolerance { tolerance: Amount },
|
||||
}
|
||||
|
||||
impl From<SendKind> for cdk::wallet::SendKind {
|
||||
fn from(kind: SendKind) -> Self {
|
||||
match kind {
|
||||
SendKind::OnlineExact => cdk::wallet::SendKind::OnlineExact,
|
||||
SendKind::OnlineTolerance { tolerance } => {
|
||||
cdk::wallet::SendKind::OnlineTolerance(tolerance.into())
|
||||
}
|
||||
SendKind::OfflineExact => cdk::wallet::SendKind::OfflineExact,
|
||||
SendKind::OfflineTolerance { tolerance } => {
|
||||
cdk::wallet::SendKind::OfflineTolerance(tolerance.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::SendKind> for SendKind {
|
||||
fn from(kind: cdk::wallet::SendKind) -> Self {
|
||||
match kind {
|
||||
cdk::wallet::SendKind::OnlineExact => SendKind::OnlineExact,
|
||||
cdk::wallet::SendKind::OnlineTolerance(tolerance) => SendKind::OnlineTolerance {
|
||||
tolerance: tolerance.into(),
|
||||
},
|
||||
cdk::wallet::SendKind::OfflineExact => SendKind::OfflineExact,
|
||||
cdk::wallet::SendKind::OfflineTolerance(tolerance) => SendKind::OfflineTolerance {
|
||||
tolerance: tolerance.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Send options
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct SendOptions {
|
||||
/// Memo
|
||||
pub memo: Option<SendMemo>,
|
||||
/// Spending conditions
|
||||
pub conditions: Option<SpendingConditions>,
|
||||
/// Amount split target
|
||||
pub amount_split_target: SplitTarget,
|
||||
/// Send kind
|
||||
pub send_kind: SendKind,
|
||||
/// Include fee
|
||||
pub include_fee: bool,
|
||||
/// Maximum number of proofs to include in the token
|
||||
pub max_proofs: Option<u32>,
|
||||
/// Metadata
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for SendOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
memo: None,
|
||||
conditions: None,
|
||||
amount_split_target: SplitTarget::None,
|
||||
send_kind: SendKind::OnlineExact,
|
||||
include_fee: false,
|
||||
max_proofs: None,
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SendOptions> for cdk::wallet::SendOptions {
|
||||
fn from(opts: SendOptions) -> Self {
|
||||
cdk::wallet::SendOptions {
|
||||
memo: opts.memo.map(Into::into),
|
||||
conditions: opts.conditions.and_then(|c| c.try_into().ok()),
|
||||
amount_split_target: opts.amount_split_target.into(),
|
||||
send_kind: opts.send_kind.into(),
|
||||
include_fee: opts.include_fee,
|
||||
max_proofs: opts.max_proofs.map(|p| p as usize),
|
||||
metadata: opts.metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::SendOptions> for SendOptions {
|
||||
fn from(opts: cdk::wallet::SendOptions) -> Self {
|
||||
Self {
|
||||
memo: opts.memo.map(Into::into),
|
||||
conditions: opts.conditions.map(Into::into),
|
||||
amount_split_target: opts.amount_split_target.into(),
|
||||
send_kind: opts.send_kind.into(),
|
||||
include_fee: opts.include_fee,
|
||||
max_proofs: opts.max_proofs.map(|p| p as u32),
|
||||
metadata: opts.metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SendOptions {
|
||||
/// Convert SendOptions to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode SendOptions from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_send_options(json: String) -> Result<SendOptions, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode SendOptions to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_send_options(options: SendOptions) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&options)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible SecretKey
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
#[serde(transparent)]
|
||||
pub struct SecretKey {
|
||||
/// Hex-encoded secret key (64 characters)
|
||||
pub hex: String,
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
/// Create a new SecretKey from hex string
|
||||
pub fn from_hex(hex: String) -> Result<Self, FfiError> {
|
||||
// Validate hex string length (should be 64 characters for 32 bytes)
|
||||
if hex.len() != 64 {
|
||||
return Err(FfiError::InvalidHex {
|
||||
msg: "Secret key hex must be exactly 64 characters (32 bytes)".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Validate hex format
|
||||
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(FfiError::InvalidHex {
|
||||
msg: "Secret key hex contains invalid characters".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self { hex })
|
||||
}
|
||||
|
||||
/// Generate a random secret key
|
||||
pub fn random() -> Self {
|
||||
use cdk::nuts::SecretKey as CdkSecretKey;
|
||||
let secret_key = CdkSecretKey::generate();
|
||||
Self {
|
||||
hex: secret_key.to_secret_hex(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SecretKey> for cdk::nuts::SecretKey {
|
||||
fn from(key: SecretKey) -> Self {
|
||||
// This will panic if hex is invalid, but we validate in from_hex()
|
||||
cdk::nuts::SecretKey::from_hex(&key.hex).expect("Invalid secret key hex")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::SecretKey> for SecretKey {
|
||||
fn from(key: cdk::nuts::SecretKey) -> Self {
|
||||
Self {
|
||||
hex: key.to_secret_hex(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Receive options
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
|
||||
pub struct ReceiveOptions {
|
||||
/// Amount split target
|
||||
pub amount_split_target: SplitTarget,
|
||||
/// P2PK signing keys
|
||||
pub p2pk_signing_keys: Vec<SecretKey>,
|
||||
/// Preimages for HTLC conditions
|
||||
pub preimages: Vec<String>,
|
||||
/// Metadata
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for ReceiveOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
amount_split_target: SplitTarget::None,
|
||||
p2pk_signing_keys: Vec::new(),
|
||||
preimages: Vec::new(),
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReceiveOptions> for cdk::wallet::ReceiveOptions {
|
||||
fn from(opts: ReceiveOptions) -> Self {
|
||||
cdk::wallet::ReceiveOptions {
|
||||
amount_split_target: opts.amount_split_target.into(),
|
||||
p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
|
||||
preimages: opts.preimages,
|
||||
metadata: opts.metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::ReceiveOptions> for ReceiveOptions {
|
||||
fn from(opts: cdk::wallet::ReceiveOptions) -> Self {
|
||||
Self {
|
||||
amount_split_target: opts.amount_split_target.into(),
|
||||
p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
|
||||
preimages: opts.preimages,
|
||||
metadata: opts.metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReceiveOptions {
|
||||
/// Convert ReceiveOptions to JSON string
|
||||
pub fn to_json(&self) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode ReceiveOptions from JSON string
|
||||
#[uniffi::export]
|
||||
pub fn decode_receive_options(json: String) -> Result<ReceiveOptions, FfiError> {
|
||||
Ok(serde_json::from_str(&json)?)
|
||||
}
|
||||
|
||||
/// Encode ReceiveOptions to JSON string
|
||||
#[uniffi::export]
|
||||
pub fn encode_receive_options(options: ReceiveOptions) -> Result<String, FfiError> {
|
||||
Ok(serde_json::to_string(&options)?)
|
||||
}
|
||||
|
||||
/// FFI-compatible PreparedSend
|
||||
#[derive(Debug, uniffi::Object)]
|
||||
pub struct PreparedSend {
|
||||
inner: Mutex<Option<cdk::wallet::PreparedSend>>,
|
||||
id: String,
|
||||
amount: Amount,
|
||||
proofs: Proofs,
|
||||
}
|
||||
|
||||
impl From<cdk::wallet::PreparedSend> for PreparedSend {
|
||||
fn from(prepared: cdk::wallet::PreparedSend) -> Self {
|
||||
let id = format!("{:?}", prepared); // Use debug format as ID
|
||||
let amount = prepared.amount().into();
|
||||
let proofs = prepared
|
||||
.proofs()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|p| std::sync::Arc::new(p.into()))
|
||||
.collect();
|
||||
Self {
|
||||
inner: Mutex::new(Some(prepared)),
|
||||
id,
|
||||
amount,
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
impl PreparedSend {
|
||||
/// Get the prepared send ID
|
||||
pub fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
/// Get the amount to send
|
||||
pub fn amount(&self) -> Amount {
|
||||
self.amount
|
||||
}
|
||||
|
||||
/// Get the proofs that will be used
|
||||
pub fn proofs(&self) -> Proofs {
|
||||
self.proofs.clone()
|
||||
}
|
||||
|
||||
/// Get the total fee for this send operation
|
||||
pub fn fee(&self) -> Amount {
|
||||
if let Ok(guard) = self.inner.lock() {
|
||||
if let Some(ref inner) = *guard {
|
||||
inner.fee().into()
|
||||
} else {
|
||||
Amount::new(0)
|
||||
}
|
||||
} else {
|
||||
Amount::new(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirm the prepared send and create a token
|
||||
pub async fn confirm(
|
||||
self: std::sync::Arc<Self>,
|
||||
memo: Option<String>,
|
||||
) -> Result<Token, FfiError> {
|
||||
let inner = {
|
||||
if let Ok(mut guard) = self.inner.lock() {
|
||||
guard.take()
|
||||
} else {
|
||||
return Err(FfiError::Generic {
|
||||
msg: "Failed to acquire lock on PreparedSend".to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(inner) = inner {
|
||||
let send_memo = memo.map(|m| cdk::wallet::SendMemo::for_token(&m));
|
||||
let token = inner.confirm(send_memo).await?;
|
||||
Ok(token.into())
|
||||
} else {
|
||||
Err(FfiError::Generic {
|
||||
msg: "PreparedSend has already been consumed or cancelled".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel the prepared send operation
|
||||
pub async fn cancel(self: std::sync::Arc<Self>) -> Result<(), FfiError> {
|
||||
let inner = {
|
||||
if let Ok(mut guard) = self.inner.lock() {
|
||||
guard.take()
|
||||
} else {
|
||||
return Err(FfiError::Generic {
|
||||
msg: "Failed to acquire lock on PreparedSend".to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(inner) = inner {
|
||||
inner.cancel().await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FfiError::Generic {
|
||||
msg: "PreparedSend has already been consumed or cancelled".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible Melted result
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct Melted {
|
||||
pub state: super::quote::QuoteState,
|
||||
pub preimage: Option<String>,
|
||||
pub change: Option<Proofs>,
|
||||
pub amount: Amount,
|
||||
pub fee_paid: Amount,
|
||||
}
|
||||
|
||||
// MeltQuoteState is just an alias for nut05::QuoteState, so we don't need a separate implementation
|
||||
|
||||
impl From<cdk::types::Melted> for Melted {
|
||||
fn from(melted: cdk::types::Melted) -> Self {
|
||||
Self {
|
||||
state: melted.state.into(),
|
||||
preimage: melted.preimage,
|
||||
change: melted.change.map(|proofs| {
|
||||
proofs
|
||||
.into_iter()
|
||||
.map(|p| std::sync::Arc::new(p.into()))
|
||||
.collect()
|
||||
}),
|
||||
amount: melted.amount.into(),
|
||||
fee_paid: melted.fee_paid.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-compatible MeltOptions
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum MeltOptions {
|
||||
/// MPP (Multi-Part Payments) options
|
||||
Mpp { amount: Amount },
|
||||
/// Amountless options
|
||||
Amountless { amount_msat: Amount },
|
||||
}
|
||||
|
||||
impl From<MeltOptions> for cdk::nuts::MeltOptions {
|
||||
fn from(opts: MeltOptions) -> Self {
|
||||
match opts {
|
||||
MeltOptions::Mpp { amount } => {
|
||||
let cdk_amount: cdk::Amount = amount.into();
|
||||
cdk::nuts::MeltOptions::new_mpp(cdk_amount)
|
||||
}
|
||||
MeltOptions::Amountless { amount_msat } => {
|
||||
let cdk_amount: cdk::Amount = amount_msat.into();
|
||||
cdk::nuts::MeltOptions::new_amountless(cdk_amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk::nuts::MeltOptions> for MeltOptions {
|
||||
fn from(opts: cdk::nuts::MeltOptions) -> Self {
|
||||
match opts {
|
||||
cdk::nuts::MeltOptions::Mpp { mpp } => MeltOptions::Mpp {
|
||||
amount: mpp.amount.into(),
|
||||
},
|
||||
cdk::nuts::MeltOptions::Amountless { amountless } => MeltOptions::Amountless {
|
||||
amount_msat: amountless.amount_msat.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user