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}