Repository: breez/breez-sdk-greenlight (language: python)

Issue:
Update `max_allowed_deposit` on previously created swaps if possible
If any swap address we've created in the past hasn't been used, we return that same address when `create_swap_address` is called(through `receive_payment`). 

Due to this behavior, changes introduced in
- #861

only apply to newly created swap addresses.

If it's possible to retrieve `max_allowed_deposit` using SwapperAPI without creating a new swap, we should update `max_allowed_deposit` of the unused swap with the latest limits.

In theory, you should only have a single unused address at a time and calling a new API to fetch latest maximum values for swaps created before this transitory period is perhaps going a bit overboard as these limits rarely change afaik.


Discussion / hints:
Perhaps the right thing is to save in the db the returned value from the swapper (4m sats) and on every return calculate based on the balance locally. 
> Perhaps the right thing is to save in the db the returned value from the swapper (4m sats) and on every return calculate based on the balance locally.

Yes, that's applicable for the case where value from the swapper stayed the same and I agree we should update maximum allowed deposit of unused swap each time we retrieve it based on the balance locally.

However, the main issue exists for unused swaps that were created using the old( fund manager) client, where 900k sats is returned as the maximum depositable value but server side is actually enforcing 4M. There's this discrepancy and since we won't be able to create a new address while we have an unused swap, we won't be able to retrieve & save the new value(4M sats).

We've opened this issue to investigate if it's possible to retrieve maximum limit without creating a new swap.
> Perhaps the right thing is to save in the db the returned value from the swapper (4m sats) and on every return calculate based on the balance locally.

Thing is the server does the balance check itself for nodes connected to Breez LSP. So the balance _may_ already be deducted from the max_allowed_deposit we receive from the server.  That value is not useful anymore after some time. Say the server returns 2M max_allowed_deposit. We persist that. Then you send out all your balance. Then when you want to do the swap again, we calculate the max deposit amount based on min(max_from_server, max_receivable). This would return 2M, not 4M. 

Other options I see are:
- add an api to Breez server to request only the min and max deposit amounts for a given node
- only reuse unused swaps if they have been created 'recently', within a given time interval

The second option would actually create a new swap, so I guess is not in line with this issues question.
The server validation is for breez mobile users right? Not for the sdk users that are connected to the sdk lsp. I think this coupling is not good as we see that in reality the swapper doesn't have access to the user balance..
How about leave a client side logic and let the swapper return the absolute max swap amount? 
> The server validation is for breez mobile users right? Not for the sdk users that are connected to the sdk lsp. I think this coupling is not good as we see that in reality the swapper doesn't have access to the user balance.. How about leave a client side logic and let the swapper return the absolute max swap amount?

> Other options I see are:
> 
>   - add an api to Breez server to request only the min and max deposit amounts for a given node

We're interested in retrieving min/max **absolutes** from server, even if the node is connected to Breez LSP, and calculate `max_allowed_deposit` against current balance after retrieving unused swap from db on client side.

Fix patch:
diff --git a/libs/sdk-bindings/src/breez_sdk.udl b/libs/sdk-bindings/src/breez_sdk.udl
index 965b958de..1fa1fd94d 100644
--- a/libs/sdk-bindings/src/breez_sdk.udl
+++ b/libs/sdk-bindings/src/breez_sdk.udl
@@ -555,6 +555,7 @@ dictionary SwapInfo {
     sequence<string> confirmed_tx_ids;
     i64 min_allowed_deposit;
     i64 max_allowed_deposit;
+    i64 max_swapper_payable;
     string? last_redeem_error;
     OpeningFeeParams? channel_opening_fees;
     u32? confirmed_at;
diff --git a/libs/sdk-core/src/backup.rs b/libs/sdk-core/src/backup.rs
index e433e059e..ef44c567d 100644
--- a/libs/sdk-core/src/backup.rs
+++ b/libs/sdk-core/src/backup.rs
@@ -780,6 +780,7 @@ mod tests {
             confirmed_tx_ids: Vec::new(),
             min_allowed_deposit: 0,
             max_allowed_deposit: 100,
+            max_swapper_payable: 200,
             last_redeem_error: None,
             channel_opening_fees: Some(get_test_ofp_48h(1, 1).into()),
             confirmed_at: Some(555),
diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs
index ad80ca8c1..c3ecb71cc 100644
--- a/libs/sdk-core/src/breez_services.rs
+++ b/libs/sdk-core/src/breez_services.rs
@@ -2697,6 +2697,7 @@ pub(crate) mod tests {
             confirmed_tx_ids: vec![],
             min_allowed_deposit: 5_000,
             max_allowed_deposit: 1_000_000,
+            max_swapper_payable: 2_000_000,
             last_redeem_error: None,
             channel_opening_fees: Some(OpeningFeeParams {
                 min_msat: 5_000_000,
@@ -3138,15 +3139,15 @@ pub(crate) mod tests {
             id: "tx1".to_string(),
             block_height: 1,
             channels_balance_msat: 100,
-            onchain_balance_msat: 1000,
+            onchain_balance_msat: 1_000,
             pending_onchain_balance_msat: 100,
             utxos: vec![],
             max_payable_msat: 95,
-            max_receivable_msat: 4000000000,
-            max_single_payment_amount_msat: 1000,
+            max_receivable_msat: 4_000_000_000,
+            max_single_payment_amount_msat: 1_000,
             max_chan_reserve_msats: 0,
             connected_peers: vec!["1111".to_string()],
-            inbound_liquidity_msats: 2000,
+            inbound_liquidity_msats: 2_000,
         }
     }
 }
diff --git a/libs/sdk-core/src/bridge_generated.rs b/libs/sdk-core/src/bridge_generated.rs
index 4f398314f..44587d938 100644
--- a/libs/sdk-core/src/bridge_generated.rs
+++ b/libs/sdk-core/src/bridge_generated.rs
@@ -2239,6 +2239,7 @@ impl support::IntoDart for SwapInfo {
             self.confirmed_tx_ids.into_into_dart().into_dart(),
             self.min_allowed_deposit.into_into_dart().into_dart(),
             self.max_allowed_deposit.into_into_dart().into_dart(),
+            self.max_swapper_payable.into_into_dart().into_dart(),
             self.last_redeem_error.into_dart(),
             self.channel_opening_fees.into_dart(),
             self.confirmed_at.into_dart(),
diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs
index 7285b8f12..9d3717f57 100644
--- a/libs/sdk-core/src/models.rs
+++ b/libs/sdk-core/src/models.rs
@@ -28,10 +28,12 @@ use crate::lnurl::pay::model::SuccessActionProcessed;
 use crate::lsp::LspInformation;
 use crate::models::Network::*;
 use crate::persist::swap::SwapChainInfo;
-use crate::swap_in::error::SwapResult;
+use crate::swap_in::error::{SwapError, SwapResult};
 use crate::swap_out::boltzswap::{BoltzApiCreateReverseSwapResponse, BoltzApiReverseSwapStatus};
 use crate::swap_out::error::{ReverseSwapError, ReverseSwapResult};
-use crate::{LNInvoice, LnUrlErrorData, LnUrlPayRequestData, LnUrlWithdrawRequestData, RouteHint};
+use crate::{
+    ensure_sdk, LNInvoice, LnUrlErrorData, LnUrlPayRequestData, LnUrlWithdrawRequestData, RouteHint,
+};
 
 pub const SWAP_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60 * 24 * 2; // 2 days
 pub const INVOICE_PAYMENT_FEE_EXPIRY_SECONDS: u32 = 60 * 60; // 60 minutes
@@ -94,10 +96,10 @@ pub struct Swap {
     pub lock_height: i64,
     pub error_message: String,
     pub required_reserve: i64,
-    /// Minimum amount, in sats, that should be sent to `bitcoin_address` for a successful swap
-    pub min_allowed_deposit: i64,
-    /// Maximum amount, in sats, that should be sent to `bitcoin_address` for a successful swap
-    pub max_allowed_deposit: i64,
+    /// Absolute minimum amount, in sats, allowed by the swapper for a successful swap
+    pub swapper_min_payable: i64,
+    /// Absolute maximum amount, in sats, allowed by the swapper for a successful swap
+    pub swapper_max_payable: i64,
 }
 
 /// Trait covering functionality involving swaps
@@ -1374,10 +1376,12 @@ pub struct SwapInfo {
     pub unconfirmed_tx_ids: Vec<String>,
     /// Transaction IDs that have been confirmed on-chain.
     pub confirmed_tx_ids: Vec<String>,
-    /// The minimum amount of sats one can send in order for the swap to succeed. Received from [SwapperAPI::create_swap].   
+    /// The minimum amount of sats one can send in order for the swap to succeed. Received from [SwapperAPI::create_swap].
     pub min_allowed_deposit: i64,
-    /// The maximum amount of sats one can send in order for the swap to succeed. Received from [SwapperAPI::create_swap].
+    /// The maximum amount of sats one can send in order for the swap to succeed. This is determined based on `max_swapper_payable` and the node's local balance.
     pub max_allowed_deposit: i64,
+    /// The absolute maximum value payable by the swapper. Received from [SwapperAPI::create_swap].
+    pub max_swapper_payable: i64,
     /// Error reason for when swap fails.
     pub last_redeem_error: Option<String>,
     /// The dynamic fees which is set if a channel opening is needed.
@@ -1475,6 +1479,14 @@ impl SwapInfo {
             _ => SwapStatus::Initial,
         }
     }
+
+    pub(crate) fn validate_swap_limits(&self) -> SwapResult<()> {
+        ensure_sdk!(
+            self.max_allowed_deposit >= self.min_allowed_deposit,
+            SwapError::unsupported_swap_limits("No allowed deposit amounts")
+        );
+        Ok(())
+    }
 }
 
 pub(crate) fn parse_short_channel_id(id_str: &str) -> Result<u64> {
diff --git a/libs/sdk-core/src/persist/migrations.rs b/libs/sdk-core/src/persist/migrations.rs
index 95a8e5116..f04eb451c 100644
--- a/libs/sdk-core/src/persist/migrations.rs
+++ b/libs/sdk-core/src/persist/migrations.rs
@@ -427,6 +427,11 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
        ALTER TABLE swaps_info ADD COLUMN total_incoming_txs INTEGER;
        UPDATE swaps_info SET status = 0;
        ",
+       // Add max absolute value payable by the swapper. For existing swaps, initialize it to max_allowed_deposit.
+       "
+       ALTER TABLE sync.swaps ADD COLUMN max_swapper_payable INTEGER NOT NULL;
+       UPDATE sync.swaps SET max_swapper_payable = max_allowed_deposit;
+       "
     ]
 }
 
diff --git a/libs/sdk-core/src/persist/swap.rs b/libs/sdk-core/src/persist/swap.rs
index 2c8f12b8d..687462588 100644
--- a/libs/sdk-core/src/persist/swap.rs
+++ b/libs/sdk-core/src/persist/swap.rs
@@ -36,9 +36,10 @@ impl SqliteStorage {
            swapper_public_key, 
            script,
            min_allowed_deposit, 
-           max_allowed_deposit
+           max_allowed_deposit,
+           max_swapper_payable
          )
-         VALUES (:bitcoin_address, :created_at, :lock_height, :payment_hash, :preimage, :private_key, :public_key, :swapper_public_key, :script, :min_allowed_deposit, :max_allowed_deposit)",
+         VALUES (:bitcoin_address, :created_at, :lock_height, :payment_hash, :preimage, :private_key, :public_key, :swapper_public_key, :script, :min_allowed_deposit, :max_allowed_deposit, :max_swapper_payable)",
          named_params! {
              ":bitcoin_address": swap_info.bitcoin_address,
              ":created_at": swap_info.created_at,
@@ -50,7 +51,8 @@ impl SqliteStorage {
              ":swapper_public_key": swap_info.swapper_public_key,            
              ":script": swap_info.script,             
              ":min_allowed_deposit": swap_info.min_allowed_deposit,
-             ":max_allowed_deposit": swap_info.max_allowed_deposit
+             ":max_allowed_deposit": swap_info.max_allowed_deposit,
+             ":max_swapper_payable": swap_info.max_swapper_payable,
          },
         )?;
 
@@ -111,6 +113,22 @@ impl SqliteStorage {
         Ok(())
     }
 
+    pub(crate) fn update_swap_max_allowed_deposit(
+        &self,
+        bitcoin_address: String,
+        max_allowed_deposit: i64,
+    ) -> PersistResult<()> {
+        self.get_connection()?.execute(
+            "UPDATE sync.swaps SET max_allowed_deposit=:max_allowed_deposit where bitcoin_address=:bitcoin_address",
+            named_params! {
+             ":max_allowed_deposit": max_allowed_deposit,
+             ":bitcoin_address": bitcoin_address,
+            },
+        )?;
+
+        Ok(())
+    }
+
     pub(crate) fn update_swap_redeem_error(
         &self,
         bitcoin_address: String,
@@ -225,6 +243,7 @@ impl SqliteStorage {
           script as {prefix}script,
           min_allowed_deposit as {prefix}min_allowed_deposit,
           max_allowed_deposit as {prefix}max_allowed_deposit,
+          max_swapper_payable as {prefix}max_swapper_payable,
           bolt11 as {prefix}bolt11,
           paid_msat as {prefix}paid_msat,
           unconfirmed_sats as {prefix}unconfirmed_sats,
@@ -267,6 +286,7 @@ impl SqliteStorage {
           {prefix}script,
           {prefix}min_allowed_deposit,
           {prefix}max_allowed_deposit,
+          {prefix}max_swapper_payable,
           {prefix}bolt11,
           {prefix}paid_msat,
           {prefix}unconfirmed_sats,
@@ -389,6 +409,7 @@ impl SqliteStorage {
             confirmed_tx_ids: confirmed_txs_raw.0,
             min_allowed_deposit: row.get(format!("{prefix}min_allowed_deposit").as_str())?,
             max_allowed_deposit: row.get(format!("{prefix}max_allowed_deposit").as_str())?,
+            max_swapper_payable: row.get(format!("{prefix}max_swapper_payable").as_str())?,
             last_redeem_error: row.get(format!("{prefix}last_redeem_error").as_str())?,
             channel_opening_fees: row.get(format!("{prefix}channel_opening_fees").as_str())?,
             confirmed_at: row.get(format!("{prefix}confirmed_at").as_str())?,
@@ -440,6 +461,7 @@ mod tests {
             confirmed_tx_ids: Vec::new(),
             min_allowed_deposit: 0,
             max_allowed_deposit: 100,
+            max_swapper_payable: 200,
             last_redeem_error: None,
             channel_opening_fees: Some(get_test_ofp_48h(1, 1).into()),
             confirmed_at: None,
diff --git a/libs/sdk-core/src/persist/sync.rs b/libs/sdk-core/src/persist/sync.rs
index 61fbe17bb..a4afb2926 100644
--- a/libs/sdk-core/src/persist/sync.rs
+++ b/libs/sdk-core/src/persist/sync.rs
@@ -141,7 +141,8 @@ impl SqliteStorage {
            swapper_public_key,
            script,
            min_allowed_deposit,
-           max_allowed_deposit
+           max_allowed_deposit,
+           max_swapper_payable
           FROM remote_sync.swaps
           WHERE bitcoin_address NOT IN (SELECT bitcoin_address FROM sync.swaps);",
             [],
@@ -469,6 +470,7 @@ mod tests {
             confirmed_tx_ids: Vec::new(),
             min_allowed_deposit: 0,
             max_allowed_deposit: 100,
+            max_swapper_payable: 200,
             last_redeem_error: None,
             channel_opening_fees: Some(get_test_ofp_48h(random(), random()).into()),
             confirmed_at: None,
diff --git a/libs/sdk-core/src/persist/transactions.rs b/libs/sdk-core/src/persist/transactions.rs
index 7b10c5fab..9c847ab27 100644
--- a/libs/sdk-core/src/persist/transactions.rs
+++ b/libs/sdk-core/src/persist/transactions.rs
@@ -557,6 +557,7 @@ fn test_ln_transactions() -> PersistResult<(), Box<dyn std::error::Error>> {
         confirmed_tx_ids: vec![],
         min_allowed_deposit: 5_000,
         max_allowed_deposit: 1_000_000,
+        max_swapper_payable: 2_000_000,
         last_redeem_error: None,
         channel_opening_fees: Some(OpeningFeeParams {
             min_msat: 5_000_000,
diff --git a/libs/sdk-core/src/swap_in/error.rs b/libs/sdk-core/src/swap_in/error.rs
index d904fca1b..883a83b43 100644
--- a/libs/sdk-core/src/swap_in/error.rs
+++ b/libs/sdk-core/src/swap_in/error.rs
@@ -14,6 +14,14 @@ pub enum SwapError {
 
     #[error("Service connectivity: {0}")]
     ServiceConnectivity(anyhow::Error),
+
+    #[error("Unsupported swap limits: {0}")]
+    UnsupportedSwapLimits(anyhow::Error),
+}
+impl SwapError {
+    pub(crate) fn unsupported_swap_limits(err: &str) -> Self {
+        Self::UnsupportedSwapLimits(anyhow!(err.to_string()))
+    }
 }
 
 impl From<SdkError> for SwapError {
diff --git a/libs/sdk-core/src/swap_in/swap.rs b/libs/sdk-core/src/swap_in/swap.rs
index 5a5e8733a..b551d54dd 100644
--- a/libs/sdk-core/src/swap_in/swap.rs
+++ b/libs/sdk-core/src/swap_in/swap.rs
@@ -55,10 +55,10 @@ impl SwapperAPI for BreezServer {
             bitcoin_address: result.address,
             swapper_pubkey: result.pubkey,
             lock_height: result.lock_height,
-            max_allowed_deposit: result.max_allowed_deposit,
+            swapper_min_payable: result.min_allowed_deposit,
+            swapper_max_payable: result.max_allowed_deposit,
             error_message: result.error_message,
             required_reserve: result.required_reserve,
-            min_allowed_deposit: result.min_allowed_deposit,
         })
     }
 
@@ -181,13 +181,38 @@ impl BTCReceiveSwap {
             .get_node_state()?
             .ok_or(anyhow!("Node info not found"))?;
 
+        // Calculate max_allowed_deposit based on absolute max and current node state
+        let fn_max_allowed_deposit = |max_allowed_deposit_abs: i64| {
+            std::cmp::min(
+                (node_state.max_receivable_msat / 1000) as i64,
+                max_allowed_deposit_abs,
+            )
+        };
+
         // check first that we don't already have an unused swap
         if let Some(unused_swap) = self.list_unused()?.first().cloned() {
             info!("Found unused swap when trying to create new swap address");
+            let bitcoin_address = unused_swap.bitcoin_address.clone();
+
+            // Check max_allowed_deposit and, if it changed, persist and validate changes
+            let current_max = fn_max_allowed_deposit(unused_swap.max_swapper_payable);
+            let res_swap = match current_max == unused_swap.max_allowed_deposit {
+                true => unused_swap,
+                false => {
+                    info!("max_allowed_deposit for this swap has changed, updating it");
+                    let mut new_swap = unused_swap.clone();
+
+                    new_swap.max_allowed_deposit = current_max;
+                    new_swap.validate_swap_limits()?;
+                    self.persister
+                        .update_swap_max_allowed_deposit(bitcoin_address.clone(), current_max)?;
+                    new_swap
+                }
+            };
 
             self.persister
-                .update_swap_fees(unused_swap.bitcoin_address.clone(), channel_opening_fees)?;
-            return Ok(unused_swap);
+                .update_swap_fees(bitcoin_address, channel_opening_fees)?;
+            return Ok(res_swap);
         }
 
         // create fresh swap keys
@@ -198,9 +223,9 @@ impl BTCReceiveSwap {
         // use swap API to fetch a new swap address
         let swap_reply = self
             .swapper_api
-            .create_swap(hash.clone(), pubkey.clone(), node_state.id)
+            .create_swap(hash.clone(), pubkey.clone(), node_state.id.clone())
             .await?;
-        info!("created swap address {}", swap_reply.max_allowed_deposit);
+        info!("created swap address {}", swap_reply.bitcoin_address);
         // calculate the submarine swap script
         let our_script = create_submarine_swap_script(
             hash.clone(),
@@ -217,13 +242,6 @@ impl BTCReceiveSwap {
             return Err(SwapError::Generic(anyhow!("Wrong address: {address_str}")));
         }
 
-        let max_allowed_deposit = std::cmp::min(
-            (node_state.max_receivable_msat / 1000) as i64,
-            swap_reply.max_allowed_deposit,
-        );
-        if max_allowed_deposit < swap_reply.min_allowed_deposit {
-            return Err(SwapError::Generic(anyhow!("No allowed deposit amounts")));
-        }
         let swap_info = SwapInfo {
             bitcoin_address: swap_reply.bitcoin_address,
             created_at: SystemTime::now()
@@ -246,12 +264,14 @@ impl BTCReceiveSwap {
             confirmed_tx_ids: Vec::new(),
             unconfirmed_tx_ids: Vec::new(),
             status: SwapStatus::Initial,
-            min_allowed_deposit: swap_reply.min_allowed_deposit,
-            max_allowed_deposit,
+            min_allowed_deposit: swap_reply.swapper_min_payable,
+            max_allowed_deposit: fn_max_allowed_deposit(swap_reply.swapper_max_payable),
+            max_swapper_payable: swap_reply.swapper_max_payable,
             last_redeem_error: None,
             channel_opening_fees: Some(channel_opening_fees),
             confirmed_at: None,
         };
+        swap_info.validate_swap_limits()?;
 
         // persist the swap info
         self.persister.insert_swap(swap_info.clone())?;
@@ -865,6 +885,32 @@ mod tests {
         Ok(())
     }
 
+    #[tokio::test]
+    async fn test_swap_max_allowed_deposit() -> Result<()> {
+        let chain_service = Arc::new(MockChainService::default());
+        let (swapper, persister) = create_swapper(chain_service.clone())?;
+        let swap_info = swapper
+            .create_swap_address(get_test_ofp(10, 10, true).into())
+            .await?;
+
+        assert_eq!(swap_info.max_swapper_payable, 4_000_000);
+        assert_eq!(swap_info.max_allowed_deposit, 4_000_000);
+
+        // After changing the node's max_receivable_msat, the swap max_allowed_deposit changes as well when the swap is fetched
+        let custom_max_receivable = 1_000_000;
+        let mut dummy_node_state = get_dummy_node_state();
+        dummy_node_state.max_receivable_msat = custom_max_receivable * 1_000;
+        persister.set_node_state(&dummy_node_state)?;
+
+        let swap_info = swapper
+            .create_swap_address(get_test_ofp(10, 10, true).into())
+            .await?;
+        assert_eq!(swap_info.max_swapper_payable, 4_000_000);
+        assert_eq!(swap_info.max_allowed_deposit, custom_max_receivable as i64);
+
+        Ok(())
+    }
+
     #[tokio::test]
     async fn test_swap_statuses() -> Result<()> {
         let tip = 1000;
diff --git a/libs/sdk-flutter/lib/bridge_generated.dart b/libs/sdk-flutter/lib/bridge_generated.dart
index 9b199cef3..c94932e29 100644
--- a/libs/sdk-flutter/lib/bridge_generated.dart
+++ b/libs/sdk-flutter/lib/bridge_generated.dart
@@ -1940,9 +1940,12 @@ class SwapInfo {
   /// The minimum amount of sats one can send in order for the swap to succeed. Received from [SwapperAPI::create_swap].
   final int minAllowedDeposit;
 
-  /// The maximum amount of sats one can send in order for the swap to succeed. Received from [SwapperAPI::create_swap].
+  /// The maximum amount of sats one can send in order for the swap to succeed. This is determined based on `max_swapper_payable` and the node's local balance.
   final int maxAllowedDeposit;
 
+  /// The absolute maximum value payable by the swapper. Received from [SwapperAPI::create_swap].
+  final int maxSwapperPayable;
+
   /// Error reason for when swap fails.
   final String? lastRedeemError;
 
@@ -1977,6 +1980,7 @@ class SwapInfo {
     required this.confirmedTxIds,
     required this.minAllowedDeposit,
     required this.maxAllowedDeposit,
+    required this.maxSwapperPayable,
     this.lastRedeemError,
     this.channelOpeningFees,
     this.confirmedAt,
@@ -4135,7 +4139,7 @@ class BreezSdkCoreImpl implements BreezSdkCore {
 
   SwapInfo _wire2api_swap_info(dynamic raw) {
     final arr = raw as List<dynamic>;
-    if (arr.length != 23) throw Exception('unexpected arr length: expect 23 but see ${arr.length}');
+    if (arr.length != 24) throw Exception('unexpected arr length: expect 24 but see ${arr.length}');
     return SwapInfo(
       bitcoinAddress: _wire2api_String(arr[0]),
       createdAt: _wire2api_i64(arr[1]),
@@ -4157,9 +4161,10 @@ class BreezSdkCoreImpl implements BreezSdkCore {
       confirmedTxIds: _wire2api_StringList(arr[17]),
       minAllowedDeposit: _wire2api_i64(arr[18]),
       maxAllowedDeposit: _wire2api_i64(arr[19]),
-      lastRedeemError: _wire2api_opt_String(arr[20]),
-      channelOpeningFees: _wire2api_opt_box_autoadd_opening_fee_params(arr[21]),
-      confirmedAt: _wire2api_opt_box_autoadd_u32(arr[22]),
+      maxSwapperPayable: _wire2api_i64(arr[20]),
+      lastRedeemError: _wire2api_opt_String(arr[21]),
+      channelOpeningFees: _wire2api_opt_box_autoadd_opening_fee_params(arr[22]),
+      confirmedAt: _wire2api_opt_box_autoadd_u32(arr[23]),
     );
   }
 
diff --git a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt
index 042e6c44e..9579afe6a 100644
--- a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt
+++ b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt
@@ -3520,6 +3520,7 @@ fun asSwapInfo(swapInfo: ReadableMap): SwapInfo? {
                 "confirmedTxIds",
                 "minAllowedDeposit",
                 "maxAllowedDeposit",
+                "maxSwapperPayable",
             ),
         )
     ) {
@@ -3545,6 +3546,7 @@ fun asSwapInfo(swapInfo: ReadableMap): SwapInfo? {
     val confirmedTxIds = swapInfo.getArray("confirmedTxIds")?.let { asStringList(it) }!!
     val minAllowedDeposit = swapInfo.getDouble("minAllowedDeposit").toLong()
     val maxAllowedDeposit = swapInfo.getDouble("maxAllowedDeposit").toLong()
+    val maxSwapperPayable = swapInfo.getDouble("maxSwapperPayable").toLong()
     val lastRedeemError = if (hasNonNullKey(swapInfo, "lastRedeemError")) swapInfo.getString("lastRedeemError") else null
     val channelOpeningFees =
         if (hasNonNullKey(swapInfo, "channelOpeningFees")) {
@@ -3576,6 +3578,7 @@ fun asSwapInfo(swapInfo: ReadableMap): SwapInfo? {
         confirmedTxIds,
         minAllowedDeposit,
         maxAllowedDeposit,
+        maxSwapperPayable,
         lastRedeemError,
         channelOpeningFees,
         confirmedAt,
@@ -3604,6 +3607,7 @@ fun readableMapOf(swapInfo: SwapInfo): ReadableMap {
         "confirmedTxIds" to readableArrayOf(swapInfo.confirmedTxIds),
         "minAllowedDeposit" to swapInfo.minAllowedDeposit,
         "maxAllowedDeposit" to swapInfo.maxAllowedDeposit,
+        "maxSwapperPayable" to swapInfo.maxSwapperPayable,
         "lastRedeemError" to swapInfo.lastRedeemError,
         "channelOpeningFees" to swapInfo.channelOpeningFees?.let { readableMapOf(it) },
         "confirmedAt" to swapInfo.confirmedAt,
diff --git a/libs/sdk-react-native/ios/BreezSDKMapper.swift b/libs/sdk-react-native/ios/BreezSDKMapper.swift
index 34e0c0a2b..ab25b8e98 100644
--- a/libs/sdk-react-native/ios/BreezSDKMapper.swift
+++ b/libs/sdk-react-native/ios/BreezSDKMapper.swift
@@ -3845,6 +3845,9 @@ enum BreezSDKMapper {
         guard let maxAllowedDeposit = swapInfo["maxAllowedDeposit"] as? Int64 else {
             throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "maxAllowedDeposit", typeName: "SwapInfo"))
         }
+        guard let maxSwapperPayable = swapInfo["maxSwapperPayable"] as? Int64 else {
+            throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "maxSwapperPayable", typeName: "SwapInfo"))
+        }
         var lastRedeemError: String?
         if hasNonNilKey(data: swapInfo, key: "lastRedeemError") {
             guard let lastRedeemErrorTmp = swapInfo["lastRedeemError"] as? String else {
@@ -3886,6 +3889,7 @@ enum BreezSDKMapper {
             confirmedTxIds: confirmedTxIds,
             minAllowedDeposit: minAllowedDeposit,
             maxAllowedDeposit: maxAllowedDeposit,
+            maxSwapperPayable: maxSwapperPayable,
             lastRedeemError: lastRedeemError,
             channelOpeningFees: channelOpeningFees,
             confirmedAt: confirmedAt
@@ -3914,6 +3918,7 @@ enum BreezSDKMapper {
             "confirmedTxIds": swapInfo.confirmedTxIds,
             "minAllowedDeposit": swapInfo.minAllowedDeposit,
             "maxAllowedDeposit": swapInfo.maxAllowedDeposit,
+            "maxSwapperPayable": swapInfo.maxSwapperPayable,
             "lastRedeemError": swapInfo.lastRedeemError == nil ? nil : swapInfo.lastRedeemError,
             "channelOpeningFees": swapInfo.channelOpeningFees == nil ? nil : dictionaryOf(openingFeeParams: swapInfo.channelOpeningFees!),
             "confirmedAt": swapInfo.confirmedAt == nil ? nil : swapInfo.confirmedAt,
diff --git a/libs/sdk-react-native/src/index.ts b/libs/sdk-react-native/src/index.ts
index cf26e1aa0..0d9b3eb57 100644
--- a/libs/sdk-react-native/src/index.ts
+++ b/libs/sdk-react-native/src/index.ts
@@ -539,6 +539,7 @@ export type SwapInfo = {
     confirmedTxIds: string[]
     minAllowedDeposit: number
     maxAllowedDeposit: number
+    maxSwapperPayable: number
     lastRedeemError?: string
     channelOpeningFees?: OpeningFeeParams
     confirmedAt?: number


Test patch:
diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs
index abbd430ab..2fe502a75 100644
--- a/libs/sdk-core/src/test_utils.rs
+++ b/libs/sdk-core/src/test_utils.rs
@@ -142,10 +142,10 @@ impl SwapperAPI for MockSwapperAPI {
             bitcoin_address: address.to_string(),
             swapper_pubkey: swapper_pub_key,
             lock_height: 144,
-            max_allowed_deposit: 4000000,
+            swapper_max_payable: 4_000_000,
             error_message: "".to_string(),
             required_reserve: 0,
-            min_allowed_deposit: 3000,
+            swapper_min_payable: 3_000,
         })
     }