diff --git a/api/views.py b/api/views.py index 877d2c957..3c84f1d5c 100644 --- a/api/views.py +++ b/api/views.py @@ -269,9 +269,13 @@ def get(self, request, format=None): # 4) If order is between public and WF2 if order.status >= Order.Status.PUB and order.status < Order.Status.WF2: data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order) - if take_order.exists(): + # Use the amount from any active take_order if it exists, so both maker and taker see the same satoshis_now + active_take_order = TakeOrder.objects.filter( + order=order, expires_at__gt=timezone.now() + ).first() + if active_take_order: data["satoshis_now"] = Logics.satoshis_now( - order, take_order.first().amount + order, active_take_order.amount ) else: data["satoshis_now"] = Logics.satoshis_now(order) @@ -289,7 +293,19 @@ def get(self, request, format=None): data["longitude"] = order.longitude data["is_disputed"] = order.is_disputed data["ur_nick"] = request.user.username - data["satoshis_now"] = order.last_satoshis + + # Use order.last_satoshis except when there's a take_order with a specific amount + # (in that case, satoshis_now was already calculated correctly above using take_order.amount) + if ( + ( + not TakeOrder.objects.filter( + order=order, expires_at__gt=timezone.now() + ).exists() + ) + or order.status < Order.Status.PUB + or order.status >= Order.Status.WF2 + ): + data["satoshis_now"] = order.last_satoshis # Add whether hold invoices are LOCKED (ACCEPTED) # Is there a maker bond? If so, True if locked, False otherwise diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 724ee49df..55068efd2 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -2108,3 +2108,104 @@ def test_robot_creation_with_missing_nostr_pubkey(self): data = response.json() self.assertIn("error_code", data) self.assertEqual(data["error_code"], 7000) + + def test_satoshis_now_with_range_order_taken(self): + """ + Tests that satoshis_now is correctly calculated when a taker takes + a range order with a specific amount. The satoshis_now should reflect + the taken amount, not the order's last_satoshis. + """ + trade = Trade(self.client, take_amount=100) + trade.publish_order() + + # Get satoshis_now before taking (should be based on max_amount or mid-range) + trade.get_order(trade.maker_index) + + # Taker takes the order with a specific amount (100 USD) + trade.take_order() + + # Get order as taker - satoshis_now should be calculated for the taken amount + trade.get_order(trade.taker_index) + data = trade.response.json() + + self.assertEqual(trade.response.status_code, 200) + self.assertIn("satoshis_now", data) + taker_satoshis_now = data["satoshis_now"] + + # Get order as maker - should see the same satoshis_now (for taken amount) + trade.get_order(trade.maker_index) + maker_data = trade.response.json() + maker_satoshis_now = maker_data["satoshis_now"] + + # Both maker and taker should see the same satoshis_now for the taken amount + self.assertEqual(taker_satoshis_now, maker_satoshis_now) + + # The satoshis for 100 USD should be different from the original + # (unless 100 happens to match the calculation for the range) + self.assertIsInstance(taker_satoshis_now, int) + self.assertGreater(taker_satoshis_now, 0) + + # Cancel order to avoid leaving pending HTLCs after a successful test + trade.cancel_order() + + def test_satoshis_now_preserved_after_taker_bond_locked(self): + """ + Tests that satoshis_now is preserved correctly after taker bond is locked + (status >= WF2). At this point, satoshis_now should use order.last_satoshis. + """ + trade = Trade(self.client, take_amount=150) + trade.publish_order() + trade.take_order() + trade.lock_taker_bond() + + # After taker bond is locked, status is WF2 + trade.get_order(trade.taker_index) + data = trade.response.json() + + self.assertEqual(trade.response.status_code, 200) + self.assertEqual(data["status"], Order.Status.WF2) + self.assertIn("satoshis_now", data) + + # satoshis_now should be set and consistent + satoshis_now = data["satoshis_now"] + self.assertIsInstance(satoshis_now, int) + self.assertGreater(satoshis_now, 0) + + # Verify maker sees the same value + trade.get_order(trade.maker_index) + maker_data = trade.response.json() + self.assertEqual(maker_data["satoshis_now"], satoshis_now) + + # Cancel order to avoid leaving pending HTLCs after a successful test + trade.cancel_order() + + def test_satoshis_now_for_different_take_amounts(self): + """ + Tests that different take amounts result in different satoshis_now values + for range orders, verifying the fix prevents overwriting with last_satoshis. + """ + # First trade with take_amount=100 + trade1 = Trade(self.client, take_amount=100, maker_index=1, taker_index=2) + trade1.publish_order() + trade1.take_order() + trade1.get_order(trade1.taker_index) + satoshis_100 = trade1.response.json()["satoshis_now"] + trade1.cancel_order() + + # Second trade with take_amount=150 (different amount in the range) + trade2 = Trade(self.client, take_amount=150, maker_index=1, taker_index=2) + trade2.publish_order() + trade2.take_order() + trade2.get_order(trade2.taker_index) + satoshis_150 = trade2.response.json()["satoshis_now"] + trade2.cancel_order() + + # Different take amounts should result in different satoshis values + # (proportional to the fiat amount taken) + self.assertIsInstance(satoshis_100, int) + self.assertIsInstance(satoshis_150, int) + self.assertGreater(satoshis_100, 0) + self.assertGreater(satoshis_150, 0) + + # 150 USD should require more satoshis than 100 USD + self.assertGreater(satoshis_150, satoshis_100)