breez_sdk_spark/token_conversion/
models.rs

1use std::fmt;
2use std::str::FromStr;
3
4use flashnet::{BTC_ASSET_ADDRESS, Pool};
5use serde::{Deserialize, Serialize};
6
7use crate::SdkError;
8
9/// Default maximum slippage for conversions in basis points (0.1%)
10pub const DEFAULT_CONVERSION_MAX_SLIPPAGE_BPS: u32 = 10;
11/// Default timeout for conversion operations in seconds
12pub const DEFAULT_CONVERSION_TIMEOUT_SECS: u32 = 30;
13/// Default integrator pubkey used when executing conversions
14pub const DEFAULT_INTEGRATOR_PUBKEY: &str =
15    "037e26d9d62e0b3df2d3e66805f61de2a33914465297abf76817296a92ac3f2379";
16/// Default integrator fee BPS used when simulating/executing conversions
17pub const DEFAULT_INTEGRATOR_FEE_BPS: u32 = 5;
18
19/// Fee attribution for a conversion, indicating which side of the conversion
20/// (sent or received) the pool fee is denominated in. The two variants are
21/// mutually exclusive — a pool fee is always denominated in one asset.
22pub(crate) enum FeeSplit {
23    /// Fee is on the sent (outbound/`asset_in`) payment, denominated in `asset_in`.
24    Sent(u128),
25    /// Fee is on the received (inbound/`asset_out`) payment, denominated in `asset_out`.
26    Received(u128),
27}
28
29/// Response from estimating a conversion, used when preparing a payment that requires conversion
30#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
31#[derive(Debug, Clone, Serialize)]
32pub struct ConversionEstimate {
33    /// The conversion options used for the estimate
34    pub options: ConversionOptions,
35    /// The input amount for the conversion.
36    /// For `FromBitcoin`: the satoshis required to produce the desired token output.
37    /// For `ToBitcoin`: the token amount being converted.
38    pub amount_in: u128,
39    /// The estimated output amount from the conversion.
40    /// For `FromBitcoin`: the estimated token amount received.
41    /// For `ToBitcoin`: the estimated satoshis received.
42    pub amount_out: u128,
43    /// The fee estimated for the conversion.
44    /// Denominated in satoshis if converting from Bitcoin, otherwise in the token base units.
45    pub fee: u128,
46    /// The reason the conversion amount was adjusted, if applicable.
47    pub amount_adjustment: Option<AmountAdjustmentReason>,
48}
49
50/// The purpose of the conversion, which is used to provide context for the conversion
51/// if its related to an ongoing payment or a self-transfer.
52#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub enum ConversionPurpose {
55    /// Conversion is associated with an ongoing payment
56    OngoingPayment {
57        /// The payment request of the ongoing payment
58        payment_request: String,
59    },
60    /// Conversion is for self-transfer
61    SelfTransfer,
62    /// Conversion triggered automatically
63    AutoConversion,
64}
65
66/// Specifies how to determine the conversion amount.
67#[derive(Debug, Clone)]
68pub(crate) enum ConversionAmount {
69    /// Specify the minimum output amount - the input will be calculated.
70    /// Used for payment conversions where we know the required output.
71    MinAmountOut(u128),
72    /// Specify the exact input amount - used for auto-conversion where we know the sats balance.
73    AmountIn(u128),
74}
75
76/// The reason why a conversion amount was adjusted from the originally requested value.
77#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79pub enum AmountAdjustmentReason {
80    /// The amount was increased to meet the minimum conversion limit.
81    FlooredToMinLimit,
82    /// The amount was increased to convert the full token balance,
83    /// avoiding a remaining balance below the minimum conversion limit (token dust).
84    IncreasedToAvoidDust,
85}
86
87/// The status of the conversion
88#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
90pub enum ConversionStatus {
91    /// Conversion is in-flight (queued or started, not yet completed)
92    Pending,
93    /// The conversion was successful
94    Completed,
95    /// The conversion failed (e.g., the initial send payment failed)
96    Failed,
97    /// The conversion failed and no refund was made yet, which requires action by the SDK to
98    /// perform the refund. This can happen if there was a failure during the conversion process.
99    RefundNeeded,
100    /// The conversion failed and a refund was made
101    Refunded,
102}
103
104impl fmt::Display for ConversionStatus {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            ConversionStatus::Pending => write!(f, "pending"),
108            ConversionStatus::Completed => write!(f, "completed"),
109            ConversionStatus::Failed => write!(f, "failed"),
110            ConversionStatus::RefundNeeded => write!(f, "refund_needed"),
111            ConversionStatus::Refunded => write!(f, "refunded"),
112        }
113    }
114}
115
116impl FromStr for ConversionStatus {
117    type Err = String;
118
119    fn from_str(s: &str) -> Result<Self, Self::Err> {
120        match s {
121            "pending" => Ok(ConversionStatus::Pending),
122            "completed" => Ok(ConversionStatus::Completed),
123            "failed" => Ok(ConversionStatus::Failed),
124            "refund_needed" => Ok(ConversionStatus::RefundNeeded),
125            "refunded" => Ok(ConversionStatus::Refunded),
126            _ => Err(format!("Invalid conversion status '{s}'")),
127        }
128    }
129}
130
131#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
133pub struct ConversionInfo {
134    /// The pool id associated with the conversion
135    pub pool_id: String,
136    /// The conversion id shared by both sides of the conversion
137    pub conversion_id: String,
138    /// The status of the conversion
139    pub status: ConversionStatus,
140    /// The fee paid for the conversion
141    /// Denominated in satoshis if converting from Bitcoin, otherwise in the token base units.
142    pub fee: Option<u128>,
143    /// The purpose of the conversion
144    pub purpose: Option<ConversionPurpose>,
145    /// The reason the conversion amount was adjusted, if applicable.
146    #[serde(default)]
147    pub amount_adjustment: Option<AmountAdjustmentReason>,
148}
149
150pub(crate) struct TokenConversionPool {
151    pub(crate) asset_in_address: String,
152    pub(crate) asset_out_address: String,
153    pub(crate) pool: Pool,
154}
155
156pub(crate) struct TokenConversionResponse {
157    /// The sent payment id for the conversion
158    pub(crate) sent_payment_id: String,
159    /// The received payment id for the conversion
160    pub(crate) received_payment_id: String,
161}
162
163/// Options for conversion when fulfilling a payment. When set, the SDK will
164/// perform a conversion before fulfilling the payment. If not set, the payment
165/// will only be fulfilled if the wallet has sufficient balance of the required asset.
166#[derive(Debug, Clone, Serialize)]
167#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
168pub struct ConversionOptions {
169    /// The type of conversion to perform when fulfilling the payment
170    pub conversion_type: ConversionType,
171    /// The optional maximum slippage in basis points (1/100 of a percent) allowed when
172    /// a conversion is needed to fulfill the payment. Defaults to 10 bps (0.1%) if not set.
173    /// The conversion will fail if the actual amount received is less than
174    /// `estimated_amount * (1 - max_slippage_bps / 10_000)`.
175    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
176    pub max_slippage_bps: Option<u32>,
177    /// The optional timeout in seconds to wait for the conversion to complete
178    /// when fulfilling the payment. This timeout only concerns waiting for the received
179    /// payment of the conversion. If the timeout is reached before the conversion
180    /// is complete, the payment will fail. Defaults to 30 seconds if not set.
181    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
182    pub completion_timeout_secs: Option<u32>,
183}
184
185#[derive(Debug, Clone, Serialize, PartialEq)]
186#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
187pub enum ConversionType {
188    /// Converting from Bitcoin to a token
189    FromBitcoin,
190    /// Converting from a token to Bitcoin
191    ToBitcoin { from_token_identifier: String },
192}
193
194impl ConversionType {
195    /// Returns the asset addresses for the conversion type
196    ///
197    /// # Arguments
198    ///
199    /// * `token_identifier` - The token identifier when converting from Bitcoin to a token
200    ///
201    /// # Returns
202    ///
203    /// Result containing:
204    /// * (String, String): A tuple containing the asset in address and asset out address
205    /// * `SdkError`: If the token identifier is required but not provided
206    pub(crate) fn as_asset_addresses(
207        &self,
208        token_identifier: Option<&String>,
209    ) -> Result<(String, String), SdkError> {
210        Ok(match self {
211            ConversionType::FromBitcoin => (
212                BTC_ASSET_ADDRESS.to_string(),
213                token_identifier
214                    .ok_or(SdkError::InvalidInput(
215                        "Token identifier is required for from Bitcoin conversion".to_string(),
216                    ))?
217                    .clone(),
218            ),
219            ConversionType::ToBitcoin {
220                from_token_identifier,
221            } => (from_token_identifier.clone(), BTC_ASSET_ADDRESS.to_string()),
222        })
223    }
224}
225
226#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
227pub struct FetchConversionLimitsRequest {
228    /// The type of conversion, either from or to Bitcoin.
229    pub conversion_type: ConversionType,
230    /// The token identifier when converting to a token.
231    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
232    pub token_identifier: Option<String>,
233}
234
235#[derive(Debug, Clone, Serialize)]
236#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
237pub struct FetchConversionLimitsResponse {
238    /// The minimum amount to be converted.
239    /// Denominated in satoshis if converting from Bitcoin, otherwise in the token base units.
240    pub min_from_amount: Option<u128>,
241    /// The minimum amount to be received from the conversion.
242    /// Denominated in satoshis if converting to Bitcoin, otherwise in the token base units.
243    pub min_to_amount: Option<u128>,
244}