add option for routing_budget_ppm in /api/rewards

This commit is contained in:
jerryfletcher21
2025-07-02 21:26:42 +02:00
parent 0763b5812b
commit 997c569360
7 changed files with 67 additions and 11 deletions

View File

@ -139,7 +139,6 @@ EXP_TAKER_BOND_INVOICE = 200
# Proportional routing fee limit (fraction of total payout: % / 100)
PROPORTIONAL_ROUTING_FEE_LIMIT = 0.001
# Base flat limit fee for routing in Sats (used only when proportional is lower than this)
MIN_FLAT_ROUTING_FEE_LIMIT = 10
MIN_FLAT_ROUTING_FEE_LIMIT_REWARD = 2
# Routing timeouts
REWARDS_TIMEOUT_SECONDS = 30

View File

@ -501,7 +501,7 @@ class CLNNode:
* float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
)
) # 200 ppm or 10 sats
) # 1000 ppm or 2 sats
timeout_seconds = int(config("REWARDS_TIMEOUT_SECONDS"))
request = node_pb2.PayRequest(
bolt11=lnpayment.invoice,

View File

@ -472,7 +472,7 @@ class LNDNode:
* float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
)
) # 200 ppm or 10 sats
) # 1000 ppm or 2 sats
timeout_seconds = int(config("REWARDS_TIMEOUT_SECONDS"))
request = router_pb2.SendPaymentRequest(
payment_request=lnpayment.invoice,

View File

@ -1886,7 +1886,7 @@ class Logics:
return
@classmethod
def withdraw_rewards(cls, user, invoice):
def withdraw_rewards(cls, user, invoice, routing_budget_ppm):
# only a user with positive withdraw balance can use this
if user.robot.earned_rewards < 1:
@ -1894,14 +1894,22 @@ class Logics:
num_satoshis = user.robot.earned_rewards
routing_budget_sats = int(
max(
num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
if routing_budget_ppm is not None and routing_budget_ppm is not False:
routing_budget_sats = float(num_satoshis) * (
float(routing_budget_ppm) / 1_000_000
)
) # 1000 ppm or 10 sats
num_satoshis = int(num_satoshis - routing_budget_sats)
else:
# start deprecate in the future
routing_budget_sats = int(
max(
num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
)
) # 1000 ppm or 2 sats
routing_budget_ppm = (routing_budget_sats / float(num_satoshis)) * 1_000_000
# end deprecate
routing_budget_ppm = (routing_budget_sats / float(num_satoshis)) * 1_000_000
reward_payout = LNNode.validate_ln_invoice(
invoice, num_satoshis, routing_budget_ppm
)

View File

@ -680,6 +680,14 @@ class ClaimRewardSerializer(serializers.Serializer):
default=None,
help_text="A valid LN invoice with the reward amount to withdraw",
)
routing_budget_ppm = serializers.IntegerField(
default=0,
min_value=Decimal(0),
max_value=100_001,
allow_null=True,
required=False,
help_text="Max budget to allocate for routing in PPM",
)
class PriceSerializer(serializers.Serializer):

View File

@ -904,6 +904,7 @@ class RewardView(CreateAPIView):
return Response(status=status.HTTP_400_BAD_REQUEST)
pgp_invoice = serializer.data.get("invoice")
routing_budget_ppm = serializer.data.get("routing_budget_ppm", None)
valid_signature, invoice = verify_signed_message(
request.user.robot.public_key, pgp_invoice
@ -915,7 +916,7 @@ class RewardView(CreateAPIView):
status.HTTP_400_BAD_REQUEST,
)
valid, context = Logics.withdraw_rewards(request.user, invoice)
valid, context = Logics.withdraw_rewards(request.user, invoice, routing_budget_ppm)
if not valid:
context["successful_withdrawal"] = False

View File

@ -1732,6 +1732,45 @@ class TradeTest(BaseAPITestCase):
self.assertResponse(response)
self.assertIsInstance(response.json()["earned_rewards"], int)
# Submit reward invoice
path = reverse("reward")
invoice = add_invoice("robot", response.json()["earned_rewards"])
signed_payout_invoice = sign_message(
invoice,
passphrase_path=f"tests/robots/{trade.taker_index}/token",
private_key_path=f"tests/robots/{trade.taker_index}/enc_priv_key",
)
body = {
"invoice": signed_payout_invoice
}
response = self.client.post(path, body, **taker_headers)
self.assertEqual(response.status_code, 200)
self.assertResponse(response)
self.assertTrue(response.json()["successful_withdrawal"])
def test_withdraw_reward_after_unilateral_cancel_routing_budget(self):
"""
Tests withdraw rewards specifying routing_budget_ppm as taker after maker
cancels order unilaterally
"""
trade = Trade(self.client)
trade.publish_order()
trade.take_order()
trade.take_order_third()
trade.lock_taker_bond()
trade.cancel_order(trade.maker_index)
# Fetch amount of rewards for taker
path = reverse("robot")
taker_headers = trade.get_robot_auth(trade.taker_index)
response = self.client.get(path, **taker_headers)
self.assertEqual(response.status_code, 200)
self.assertResponse(response)
self.assertIsInstance(response.json()["earned_rewards"], int)
# Submit reward invoice
path = reverse("reward")
invoice = add_invoice("robot", response.json()["earned_rewards"])
@ -1742,6 +1781,7 @@ class TradeTest(BaseAPITestCase):
)
body = {
"invoice": signed_payout_invoice,
"routing_budget_ppm": 0
}
response = self.client.post(path, body, **taker_headers)