mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-31 03:11:42 +00:00
add option for routing_budget_ppm in /api/rewards
This commit is contained in:
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user