Skip to content
Open
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
7e82e70
Add update redeemable positions command
cyc60 Jan 8, 2026
488c6bc
Cleanup
cyc60 Jan 8, 2026
fac0134
Update api client
cyc60 Jan 12, 2026
cc6010f
Check arbitrum balance, add min shares arg
cyc60 Jan 12, 2026
743f179
Check boosted shares via subgraph
cyc60 Jan 12, 2026
f314a01
Add vault proportions
cyc60 Jan 13, 2026
096b2ea
Merge branch 'master' into redeemable-positions
cyc60 Jan 13, 2026
bf5d407
Add logs
cyc60 Jan 13, 2026
baf4d2f
Small refactoring
cyc60 Jan 13, 2026
6c85b19
Add API rate limits
cyc60 Jan 13, 2026
503c959
Update comment
cyc60 Jan 13, 2026
eb4f195
Merge branch 'master' into redeemable-positions
cyc60 Jan 13, 2026
6600e0e
Rename var
cyc60 Jan 13, 2026
14465fc
Fix typo
cyc60 Jan 13, 2026
8437f4b
Merge branch 'master' into redeemable-positions
tsudmi Jan 19, 2026
248a9be
Add tests
cyc60 Jan 19, 2026
5d4eea4
Review fixes
cyc60 Jan 19, 2026
dc38e91
Review fixes #2
cyc60 Jan 19, 2026
73899a3
Merge branch 'master' into redeemable-positions
cyc60 Jan 19, 2026
89bd7fa
Review fixes #3
cyc60 Jan 19, 2026
cb7308d
Add merkle root
cyc60 Jan 19, 2026
625c0ba
Add progress bar
cyc60 Jan 19, 2026
c455880
Simplify boosted filters
cyc60 Jan 20, 2026
1e861b2
Fetch oseth from graph
cyc60 Jan 20, 2026
06e8f81
Add OsTokenConverter
cyc60 Jan 20, 2026
32546bb
Add tests
cyc60 Jan 20, 2026
921dcd2
Install multiproof
cyc60 Jan 21, 2026
ff10073
Add process-redeemer command
cyc60 Jan 22, 2026
1e71e0f
Review fixes
cyc60 Jan 22, 2026
93b2f65
Rm OS_TOKEN_VAULT_CONTROLLER_CONTRACT_ADDRESS
cyc60 Jan 22, 2026
6d6b0fc
Add integration test for update_redeemable_positions
cyc60 Jan 22, 2026
dc7f915
Merge branch 'redeemable-positions' into process-redeemer
cyc60 Jan 22, 2026
0ec716f
Add integration test for update_redeemable_positions
cyc60 Jan 26, 2026
52f2ce1
Merge branch 'redeemable-positions' into process-redeemer
cyc60 Jan 26, 2026
5b266cd
Rename package
cyc60 Jan 26, 2026
0f689b0
Merge branch 'redeemable-positions' into process-redeemer
cyc60 Jan 26, 2026
526af08
Metavault handling
cyc60 Jan 27, 2026
06d7c71
Update tests
cyc60 Jan 27, 2026
27cb7c5
Add proofs
cyc60 Jan 27, 2026
69e0b73
Fix IPFS mock
cyc60 Jan 28, 2026
2dff8eb
Move log
cyc60 Jan 28, 2026
5d9a0ff
Copilot fixes
cyc60 Jan 28, 2026
bfb8866
Add withdrawals to cover redemption assets
evgeny-stakewise Jan 25, 2026
8c6f6d2
Add tests
evgeny-stakewise Feb 3, 2026
fd0ca47
Merge branch 'cover-redeemer-queue-3' into process-redeemer
cyc60 Feb 4, 2026
1838d66
Unify is_meta_vault
cyc60 Feb 4, 2026
079cc80
Merge branch 'v5-release' into process-redeemer
cyc60 Feb 17, 2026
211e09e
Unify redeemable positions fetch
cyc60 Feb 18, 2026
3d7a509
Fix contract calls
cyc60 Feb 18, 2026
8087439
Check empty merkle root
cyc60 Feb 18, 2026
0e8e1db
Skip empty positions
cyc60 Feb 18, 2026
9490f74
Refactoring
cyc60 Feb 18, 2026
8b2014c
Add tests
cyc60 Feb 18, 2026
2eb4378
Add interval option
cyc60 Feb 18, 2026
44dac79
Remove unused options
cyc60 Feb 19, 2026
5c116cb
Split OsTokenPosition.redeemable_shares into available_shares and sha…
cyc60 Feb 19, 2026
97b3b06
Update tests
cyc60 Feb 20, 2026
8bef13b
Merge branch 'v5-release' into process-redeemer
cyc60 Mar 5, 2026
03102ed
Use sw utils
cyc60 Mar 5, 2026
774c62b
Fix redeemable_assets filter
cyc60 Mar 5, 2026
f28c964
Extract fetch_harvest_params_by_vault from select_positions
cyc60 Mar 5, 2026
f4874ac
Refactoring
cyc60 Mar 6, 2026
a5efdbe
Add get_multiple_harvest_params
cyc60 Mar 6, 2026
bb35ee1
Fix nonce
cyc60 Mar 6, 2026
c434029
Refactor
cyc60 Mar 6, 2026
37dae63
Add tests
cyc60 Mar 6, 2026
acf74df
Refactor get_multiple_harvest_params
cyc60 Mar 6, 2026
22e846c
Single _try_redeem_meta_vault call
cyc60 Mar 9, 2026
1899d73
Refactoring
cyc60 Mar 9, 2026
1f32663
Handle zero nonce
cyc60 Mar 9, 2026
8c97ae3
Fix tests
cyc60 Mar 9, 2026
88dcdfb
Review fixes #1
cyc60 Mar 10, 2026
8c2a97c
Add min-queued-assets option
cyc60 Mar 10, 2026
1d8c5d9
Update tests
cyc60 Mar 10, 2026
a4d4608
Rm double comparison
cyc60 Mar 10, 2026
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
465 changes: 465 additions & 0 deletions src/commands/internal/process_redeemer.py

Large diffs are not rendered by default.

743 changes: 743 additions & 0 deletions src/commands/tests/test_internal/test_process_redeemer.py

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions src/common/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,36 @@ async def get_exit_queue_missing_assets(
block_identifier=block_number
)

async def positions_manager(self) -> ChecksumAddress:
return await self.contract.functions.positionsManager().call()

async def queued_shares(self, block_number: BlockNumber | None = None) -> Wei:
return await self.contract.functions.queuedShares().call(block_identifier=block_number)

async def can_process_exit_queue(self, block_number: BlockNumber | None = None) -> bool:
return await self.contract.functions.canProcessExitQueue().call(
block_identifier=block_number
)

async def process_exit_queue(self) -> HexStr:
tx_function = self.contract.functions.processExitQueue()
tx_hash = await transaction_gas_wrapper(tx_function)
return Web3.to_hex(tx_hash)

async def redeem_sub_vaults_assets(
self, vault_address: ChecksumAddress, assets_to_redeem: Wei
) -> HexStr:
tx_function = self.contract.functions.redeemSubVaultsAssets(vault_address, assets_to_redeem)
tx_hash = await transaction_gas_wrapper(tx_function)
tx_receipt = await self.execution_client.eth.wait_for_transaction_receipt(
tx_hash, timeout=settings.execution_transaction_timeout
)
if not tx_receipt['status']:
raise RuntimeError(
f'redeemSubVaultsAssets transaction failed. Tx Hash: {Web3.to_hex(tx_hash)}'
)
return Web3.to_hex(tx_hash)


class ValidatorsCheckerContract(ContractWrapper):
abi_path = 'abi/IValidatorsChecker.json'
Expand Down
10 changes: 6 additions & 4 deletions src/common/harvest.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
from hexbytes import HexBytes
from sw_utils.networks import GNO_NETWORKS
from web3 import Web3
from web3.types import BlockNumber, Wei
from web3.types import BlockNumber, ChecksumAddress, Wei

from src.common.clients import ipfs_fetch_client
from src.common.contracts import VaultContract, keeper_contract
from src.common.typings import HarvestParams
from src.config.settings import settings


async def get_harvest_params(block_number: BlockNumber | None = None) -> HarvestParams | None:
if not await keeper_contract.can_harvest(settings.vault, block_number):
async def get_harvest_params(
vault: ChecksumAddress, block_number: BlockNumber | None = None
) -> HarvestParams | None:
if not await keeper_contract.can_harvest(vault, block_number):
return None

last_rewards = await keeper_contract.get_last_rewards_update(block_number)
if last_rewards is None:
return None

vault_contract = VaultContract(settings.vault)
vault_contract = VaultContract(vault)
harvest_params = await _fetch_harvest_params_from_ipfs(
vault_contract=vault_contract,
ipfs_hash=last_rewards.ipfs_hash,
Expand Down
6 changes: 4 additions & 2 deletions src/common/startup_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,10 @@ async def _check_events_logs() -> None:


async def _check_vault_withdrawable_assets() -> None:
harvest_params = await get_harvest_params()
withdrawable_assets = await get_withdrawable_assets(harvest_params=harvest_params)
harvest_params = await get_harvest_params(settings.vault)
withdrawable_assets = await get_withdrawable_assets(
settings.vault, harvest_params=harvest_params
)

# Note. We round down assets in the log message because of the case when assets
# is slightly less than required amount to register validator.
Expand Down
2 changes: 1 addition & 1 deletion src/harvest/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async def process_block(self, interrupt_handler: InterruptHandler) -> None:
"""

# check current gas prices
harvest_params = await get_harvest_params()
harvest_params = await get_harvest_params(settings.vault)
if not harvest_params:
return

Expand Down
2 changes: 2 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from src.commands.create_wallet import create_wallet
from src.commands.exit_validators import exit_validators
from src.commands.init import init
from src.commands.internal.process_redeemer import process_redeemer
from src.commands.internal.update_redeemable_positions import (
update_redeemable_positions,
)
Expand Down Expand Up @@ -58,6 +59,7 @@ def cli() -> None:
cli.add_command(node_start)
cli.add_command(node_status)
cli.add_command(update_redeemable_positions)
cli.add_command(process_redeemer)


if __name__ == '__main__':
Expand Down
4 changes: 3 additions & 1 deletion src/redemptions/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@


batch_size = 20
ZERO_MERKLE_ROOT = HexStr('0x' + '0' * 64)


async def get_redemption_assets(chain_head: ChainHead) -> Gwei:
Expand Down Expand Up @@ -151,7 +152,8 @@ async def iter_os_token_positions(
# Check whether redeemable positions are available
if not redeemable_positions.ipfs_hash:
return

if redeemable_positions.merkle_root == ZERO_MERKLE_ROOT:
return
# Fetch redeemable positions data from IPFS
data = cast(list[dict], await ipfs_fetch_client.fetch_json(redeemable_positions.ipfs_hash))

Expand Down
2 changes: 2 additions & 0 deletions src/redemptions/typings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class OsTokenPosition:
owner: ChecksumAddress
vault: ChecksumAddress
amount: Wei
available_shares: Wei = Wei(0)
shares_to_redeem: Wei = Wei(0)

def as_dict(self) -> dict:
return {
Expand Down
2 changes: 1 addition & 1 deletion src/reward_splitter/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async def process_block(self, interrupt_handler: InterruptHandler) -> None:
'Processing fee splitter %s ',
reward_splitter.address,
)
harvest_params = await get_harvest_params()
harvest_params = await get_harvest_params(settings.vault)
exit_requests = splitter_to_exit_requests.get(reward_splitter.address, []) # nosec

reward_splitter_calls = await _get_reward_splitter_calls(
Expand Down
6 changes: 4 additions & 2 deletions src/validators/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@ def process_network_validator_event(
return None


async def get_withdrawable_assets(harvest_params: HarvestParams | None) -> Wei:
async def get_withdrawable_assets(
vault: ChecksumAddress, harvest_params: HarvestParams | None
) -> Wei:
"""Fetches vault's available assets for staking."""
vault_contract = VaultContract(settings.vault)
vault_contract = VaultContract(vault)
if harvest_params is None:
return await vault_contract.functions.withdrawableAssets().call()

Expand Down
4 changes: 2 additions & 2 deletions src/validators/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async def process(self) -> None:
await update_unused_validator_keys_metric(
keystore=self.keystore,
)
harvest_params = await get_harvest_params()
harvest_params = await get_harvest_params(settings.vault)

vault_assets = await get_vault_assets(harvest_params=harvest_params)
vault_assets = Gwei(max(0, vault_assets - settings.vault_min_balance_gwei))
Expand Down Expand Up @@ -259,7 +259,7 @@ async def register_new_validators(


async def get_vault_assets(harvest_params: HarvestParams | None) -> Gwei:
vault_assets = await get_withdrawable_assets(harvest_params=harvest_params)
vault_assets = await get_withdrawable_assets(settings.vault, harvest_params=harvest_params)
if settings.network in GNO_NETWORKS:
# apply GNO -> mGNO exchange rate
vault_assets = convert_to_mgno(vault_assets)
Expand Down
2 changes: 1 addition & 1 deletion src/withdrawals/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def get_queued_assets(
Get exit queue missing assets.
For Gno networks return value in mGNO-Gwei.
"""
harvest_params = await get_harvest_params(chain_head.block_number)
harvest_params = await get_harvest_params(settings.vault, chain_head.block_number)

# Get exit queue cumulative tickets
exit_queue_cumulative_ticket = (
Expand Down
Loading