Skip to main content

breez_sdk_spark/token_conversion/
mod.rs

1mod error;
2mod flashnet;
3mod middleware;
4mod models;
5
6pub use error::ConversionError;
7pub(crate) use flashnet::FlashnetTokenConverter;
8pub(crate) use middleware::TokenConversionMiddleware;
9pub use models::*;
10
11use std::sync::Arc;
12
13use spark_wallet::{PublicKey, TransferId};
14use tokio::sync::broadcast;
15
16use crate::{EventEmitter, RefundPendingConversionsResponse};
17
18/// Trait for conversion implementations.
19///
20/// This trait abstracts the conversion mechanics, allowing different
21/// implementations (e.g., Flashnet) to be used interchangeably.
22/// Business logic for when/how much to convert is handled by `StableBalance`.
23///
24/// Implementations are reachable from the `EventEmitter` (the stable balance
25/// middleware holds the converter), so they must not store an emitter
26/// reference; the caller passes one into [`convert`](Self::convert) instead.
27#[macros::async_trait]
28pub(crate) trait TokenConverter: Send + Sync {
29    /// Execute a conversion swap.
30    ///
31    /// # Arguments
32    /// * `event_emitter` - Emitter for the payment events of the swap legs
33    /// * `options` - The conversion options including type and slippage
34    /// * `purpose` - The purpose of the conversion
35    /// * `token_identifier` - Optional token identifier for `FromBitcoin` conversions
36    /// * `amount` - Either the minimum output amount or exact input amount
37    /// * `transfer_id` - Optional transfer ID for idempotency
38    async fn convert(
39        &self,
40        event_emitter: Arc<EventEmitter>,
41        options: &ConversionOptions,
42        purpose: &ConversionPurpose,
43        token_identifier: Option<&String>,
44        amount: ConversionAmount,
45        transfer_id: Option<TransferId>,
46    ) -> Result<TokenConversionResponse, ConversionError>;
47
48    /// The legs of the conversion whose sent leg is the transfer
49    /// `transfer_id`, recorded as completed, when the pool reports its swap
50    /// ran. Sends nothing. Errors when the pool's swaps could not be listed,
51    /// which is distinct from a swap that did not run.
52    async fn find_completed_conversion(
53        &self,
54        transfer_id: &TransferId,
55        purpose: &ConversionPurpose,
56    ) -> Result<Option<TokenConversionResponse>, ConversionError>;
57
58    /// Validate a conversion and return the estimated conversion.
59    ///
60    /// Called during `prepare_send_payment` to calculate the conversion fee,
61    /// and during auto-conversion to estimate the token output.
62    ///
63    /// # Arguments
64    /// * `options` - The conversion options to validate
65    /// * `token_identifier` - Optional token identifier for `FromBitcoin` conversions
66    /// * `amount` - Either the minimum output amount or exact input amount
67    ///
68    /// # Returns
69    /// The estimated conversion including amount and fee, or None if options is None.
70    /// `estimate.amount_in` is the input amount, `estimate.amount_out` is the estimated output.
71    async fn validate(
72        &self,
73        options: Option<&ConversionOptions>,
74        token_identifier: Option<&String>,
75        amount: ConversionAmount,
76    ) -> Result<Option<ConversionEstimate>, ConversionError>;
77
78    /// Fetch conversion limits for a given conversion type.
79    ///
80    /// # Arguments
81    /// * `request` - The request containing conversion type and optional token identifier
82    async fn fetch_limits(
83        &self,
84        request: &FetchConversionLimitsRequest,
85    ) -> Result<FetchConversionLimitsResponse, ConversionError>;
86
87    /// Runs a local and a remote pass to refund any pending conversions.
88    async fn refund_pending(
89        &self,
90        event_emitter: &EventEmitter,
91    ) -> Result<RefundPendingConversionsResponse, ConversionError>;
92
93    /// Runs a local pass to refund any pending conversions.
94    async fn refund_local_pending(
95        &self,
96        event_emitter: &EventEmitter,
97    ) -> Result<RefundPendingConversionsResponse, ConversionError>;
98
99    /// Records the swap if it ran after all, and otherwise claws the input back.
100    async fn settle_stranded_input(&self, input: StrandedInput, event_emitter: &EventEmitter);
101
102    /// Optional requests that wake the client-mode periodic refunder.
103    fn subscribe_refund_requests(&self) -> Option<broadcast::Receiver<RefundRequest>> {
104        None
105    }
106}
107
108/// How long the refunder waits before settling a stranded input, so the pool's
109/// swap listing can catch up with a swap that ran despite the failed call.
110pub(crate) const STRANDED_INPUT_SETTLE_SECS: u64 = 10;
111
112/// A swap input a failed conversion left at the pool.
113#[derive(Clone, Debug)]
114pub(crate) struct StrandedInput {
115    pub(crate) clawback_id: String,
116    pub(crate) pool_id: PublicKey,
117    pub(crate) payment_id: Option<String>,
118    pub(crate) prior_info: Option<ConversionInfo>,
119}
120
121/// Work for the client-mode conversion refunder.
122#[derive(Clone, Debug)]
123pub(crate) enum RefundRequest {
124    /// Run a pass over the payments marked `RefundNeeded`.
125    Pass,
126    /// Settle one stranded input, after the settle wait.
127    Settle(Box<StrandedInput>),
128}