Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
):

@KoalaSat KoalaSat Feb 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding not(...) with and inside is always confusing, better to just negate take_order.exists() and add regular ands for status

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
Expand Down
101 changes: 101 additions & 0 deletions tests/test_trade_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading