Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: InjectiveStaking add new compound_rewards function #4

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
90 changes: 89 additions & 1 deletion injective_functions/staking/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import asyncio
from decimal import Decimal

from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM

from injective_functions.base import InjectiveBase
from typing import Dict, List
from typing import Dict


"""This class handles all account transfer within the account"""
Expand All @@ -19,3 +23,87 @@ async def stake_tokens(self, validator_address: str, amount: str) -> Dict:
amount=float(amount),
)
return await self.chain_client.build_and_broadcast_tx(msg)

async def compound_rewards(self, validator_address: str) -> Dict:
"""
Compounds staking rewards by withdrawing them and restaking.
:param validator_address: The validator's address
:return: Transaction result
"""
try:
if not validator_address.startswith("injvaloper"):
raise ValueError("Invalid validator address format")

# Step 1: Fetch the initial INJ balance
balance_response = await self.chain_client.client.get_bank_balance(
address=self.chain_client.address.to_acc_bech32(),
denom=INJ_DENOM
)
initial_balance = Decimal(balance_response.balance.amount)

# Step 2: Withdraw rewards
withdraw_msg = self.chain_client.composer.msg_withdraw_delegator_reward(
delegator_address=self.chain_client.address.to_acc_bech32(),
validator_address=validator_address,
)
withdraw_response = await self.chain_client.build_and_broadcast_tx(withdraw_msg)
defser marked this conversation as resolved.
Show resolved Hide resolved

# Step 3: Wait for the balance to update
updated_balance = await self.wait_for_balance_update(old_balance=initial_balance, denom=INJ_DENOM)

# Step 4: Calculate the withdrawn rewards
rewards_to_stake = updated_balance - initial_balance
defser marked this conversation as resolved.
Show resolved Hide resolved
if rewards_to_stake < 0:
return {
"success": False,
"error": f"Rewards ({rewards_to_stake}) are lower than gas fees, resulting in a negative net reward."
}

if rewards_to_stake == 0:
return {"success": False, "error": "No rewards available to compound."}

# Step 5: Restake the rewards
delegate_msg = self.chain_client.composer.MsgDelegate(
delegator_address=self.chain_client.address.to_acc_bech32(),
validator_address=validator_address,
amount=rewards_to_stake / Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}"),
)
delegate_response = await self.chain_client.build_and_broadcast_tx(delegate_msg)

return {
"success": True,
"withdraw_response": withdraw_response,
"delegate_response": delegate_response,
}

except (TimeoutError, ValueError) as e:
return {"success": False, "error": str(e)}

async def wait_for_balance_update(
self,
old_balance: Decimal,
denom: str,
timeout: int = 10,
interval: int = 1
) -> Decimal:
defser marked this conversation as resolved.
Show resolved Hide resolved
"""
Waits for the balance to update after a transaction.
:param old_balance: Previous balance to compare against
:param denom: Denomination of the token (e.g., "inj")
:param timeout: Total time to wait (in seconds)
:param interval: Time between balance checks (in seconds)
:return: Updated balance
"""
if interval <= 0:
raise ValueError("Interval must be greater than zero.")

for _ in range(timeout // interval):
balance_response = await self.chain_client.client.get_bank_balance(
address=self.chain_client.address.to_acc_bech32(),
denom=denom
)
updated_balance = Decimal(balance_response.balance.amount)
if updated_balance != old_balance:
return updated_balance
await asyncio.sleep(interval)
raise TimeoutError("Balance did not update within the timeout period.")
14 changes: 14 additions & 0 deletions injective_functions/staking/staking_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
},
"required": ["validator_address", "amount"]
}
},
defser marked this conversation as resolved.
Show resolved Hide resolved
{
"name": "compound_rewards",
"description": "Automatically reinvest your staking rewards with a specific validator to increase your staked amount.",
defser marked this conversation as resolved.
Show resolved Hide resolved
"parameters": {
"type": "object",
"properties": {
"validator_address": {
"type": "string",
"description": "Validator address you want to compound your rewards with."
}
},
"required": ["validator_address"]
}
}
]
}
1 change: 1 addition & 0 deletions injective_functions/utils/function_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class InjectiveFunctionMapper:
"query_total_supply": ("bank", "query_total_supply"),
# Staking functions
"stake_tokens": ("staking", "stake_tokens"),
"compound_rewards": ("staking", "compound_rewards"),
# Auction functions
"send_bid_auction": ("auction", "send_bid_auction"),
# Authz functions
Expand Down