Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
50640b3
Dynamic update leverage
alexey-kichin Nov 23, 2025
a817b8b
chore: update v4-proto dependency to version 9.4.0 in pyproject.toml
alexey-kichin Nov 23, 2025
5bce6cd
update poentry lock file
alexey-kichin Nov 23, 2025
5ce8324
chore: update dependencies in poetry.lock and pyproject.toml
alexey-kichin Nov 23, 2025
a508767
Add examples for dynamic leverage
alexey-kichin Dec 2, 2025
742e3ee
fix lint
alexey-kichin Dec 7, 2025
0061319
Merge branch 'main' into kichal/dynamic-leverage-getter-setter-python
alexey-kichin Dec 7, 2025
8623241
trigger the pipelines
alexey-kichin Dec 11, 2025
6859cec
Enhance leverage example with detailed position management and collat…
alexey-kichin Dec 11, 2025
52d3197
Refactor leverage example for improved position handling and clarity
alexey-kichin Dec 11, 2025
9abd01e
fix lint
alexey-kichin Dec 12, 2025
cd1dfb1
Add pauses for UI inspection in leverage example
alexey-kichin Dec 15, 2025
0819e67
Refactor leverage example to support dynamic market handling
alexey-kichin Dec 15, 2025
2218f07
Refactor leverage example for improved function naming and clarity
alexey-kichin Dec 15, 2025
f014fc9
Update leverage example to support ETH market and enhance position ma…
alexey-kichin Dec 15, 2025
c418be7
leverage script to restore initial position
alexey-kichin Dec 15, 2025
34fa249
fix lint
alexey-kichin Dec 15, 2025
1281bce
Leave only set/get leverage examples
alexey-kichin Dec 16, 2025
dc663ad
trigger pipeline
alexey-kichin Dec 19, 2025
508be85
trigger pipeline
alexey-kichin Dec 19, 2025
672fa66
Update minimum balance requirement in balance checker function
alexey-kichin Dec 19, 2025
6762d0b
trigger pipeline
alexey-kichin Dec 19, 2025
779b708
trigger build
alexey-kichin Dec 28, 2025
4a5c65b
trigger build
alexey-kichin Jan 3, 2026
3947bf4
trigger build
alexey-kichin Jan 5, 2026
d2978ba
disable account balance check for now
alexey-kichin Jan 12, 2026
1ae8bc4
switch to ENA
alexey-kichin Jan 14, 2026
d9c13d3
Revert "switch to ENA"
alexey-kichin Jan 16, 2026
9e539b9
Reapply "switch to ENA"
alexey-kichin Jan 16, 2026
ce50721
add liquidity setup and one test for liquidity
alexey-kichin Jan 16, 2026
fb8d332
print more details why test fails
alexey-kichin Jan 16, 2026
e9f1d93
add 3rd test account details
alexey-kichin Jan 19, 2026
b533dd7
migrate to the new testing account
alexey-kichin Jan 19, 2026
7ba765b
fix some trade tests with new account and provision of liquidity from…
alexey-kichin Jan 19, 2026
117c57e
fix: test_mutating_node_client.py, close BTC orders
varex83 Jan 19, 2026
270b226
fix lint
alexey-kichin Jan 19, 2026
f98d176
fix: remaining serialization tests and test_withdraw_delegate_reward
varex83 Jan 19, 2026
d2ded2c
Merge branch 'kichal/dynamic-leverage-getter-setter-python' of https:…
varex83 Jan 19, 2026
aae2380
refactor: improve readability of serialization tests by formatting fu…
alexey-kichin Jan 19, 2026
5fec805
Merge branch 'main' into kichal/dynamic-leverage-getter-setter-python
alexey-kichin Jan 19, 2026
68728a9
keep permissioned key example
alexey-kichin Jan 19, 2026
447a950
Merge branch 'main' into kichal/dynamic-leverage-getter-setter-python
alexey-kichin Jan 21, 2026
eaaa07e
feat: add script for transferring assets between subaccounts on dYdX …
alexey-kichin Jan 21, 2026
ef78b2b
refactor: remove transfer_between_subaccounts script and add withdraw…
alexey-kichin Jan 21, 2026
9ce0a73
trigger rebuild
alexey-kichin Jan 21, 2026
95cbccf
Revert "trigger rebuild"
alexey-kichin Jan 21, 2026
8ebf6b3
fix: increase withdrawal amount quantum to 10,000,000 for better fund…
alexey-kichin Jan 21, 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
4 changes: 2 additions & 2 deletions .github/workflows/py-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ jobs:
- name: Install Dependencies
run: poetry install

- name: Check Funding Account Balance
run: poetry run python dydx_v4_client/node/balance_checker.py
# - name: Check Funding Account Balance
# run: poetry run python dydx_v4_client/node/balance_checker.py

- name: Run pytest
run: poetry run pytest
48 changes: 48 additions & 0 deletions v4-client-py-v2/dydx_v4_client/node/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
from v4_proto.dydxprotocol.clob.query_pb2 import (
QueryAllClobPairRequest,
QueryClobPairAllResponse,
QueryLeverageRequest,
QueryLeverageResponse,
)
from v4_proto.dydxprotocol.feetiers import query_pb2 as fee_tier_query
from v4_proto.dydxprotocol.feetiers import query_pb2_grpc as fee_tier_query_grpc
Expand Down Expand Up @@ -124,6 +126,7 @@
withdraw_delegator_reward,
undelegate,
delegate,
update_leverage,
)
from dydx_v4_client.wallet import Wallet

Expand Down Expand Up @@ -326,6 +329,24 @@ async def get_clob_pairs(self) -> QueryClobPairAllResponse:
stub = clob_query_grpc.QueryStub(self.channel)
return stub.ClobPairAll(QueryAllClobPairRequest())

async def get_leverage(
self, address: str, subaccount_number: int
) -> QueryLeverageResponse:
"""
Retrieves leverage information for a subaccount.

Args:
address (str): The subaccount owner address.
subaccount_number (int): The subaccount number.

Returns:
QueryLeverageResponse: The response containing leverage information.
"""
stub = clob_query_grpc.QueryStub(self.channel)
return stub.Leverage(
QueryLeverageRequest(owner=address, number=subaccount_number)
)

async def get_price(self, market_id: int) -> market_price_type.MarketPrice:
"""
Retrieves the market price for a given market ID.
Expand Down Expand Up @@ -1077,6 +1098,33 @@ async def batch_cancel_orders(
tx_options=tx_options,
)

async def update_leverage(
self,
wallet: Wallet,
address: str,
subaccount_number: int,
entries: List,
tx_options: Optional[TxOptions] = None,
):
"""
Updates leverage for a subaccount.

Args:
wallet (Wallet): The wallet to use for signing the transaction.
address (str): The subaccount owner address.
subaccount_number (int): The subaccount number.
entries (List): List of LeverageEntry objects.
tx_options (TxOptions, optional): Options for transaction to support authenticators.

Returns:
The response from the transaction broadcast.
"""
return await self.broadcast_message(
wallet,
update_leverage(address, subaccount_number, entries),
tx_options=tx_options,
)

async def add_authenticator(
self,
wallet: Wallet,
Expand Down
24 changes: 23 additions & 1 deletion v4-client-py-v2/dydx_v4_client/node/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
BuilderCodeParameters,
TwapParameters,
)
from v4_proto.dydxprotocol.clob.tx_pb2 import MsgCancelOrder, MsgPlaceOrder
from v4_proto.dydxprotocol.clob.tx_pb2 import (
MsgCancelOrder,
MsgPlaceOrder,
MsgUpdateLeverage,
)
from v4_proto.dydxprotocol.listing.tx_pb2 import MsgCreateMarketPermissionless
from v4_proto.dydxprotocol.sending.transfer_pb2 import (
MsgDepositToSubaccount,
Expand Down Expand Up @@ -275,3 +279,21 @@ def twap_parameters(duration: int, interval: int, price_tolerance: int):
return TwapParameters(
duration=duration, interval=interval, price_tolerance=price_tolerance
)


def update_leverage(address: str, subaccount_number: int, entries: List):
"""
Create a MsgUpdateLeverage message.

Args:
address (str): The subaccount owner address.
subaccount_number (int): The subaccount number.
entries (List): List of LeverageEntry objects.

Returns:
MsgUpdateLeverage: The update leverage message.
"""
return MsgUpdateLeverage(
subaccount_id=SubaccountId(owner=address, number=subaccount_number),
clob_pair_leverage=entries,
)
148 changes: 148 additions & 0 deletions v4-client-py-v2/examples/leverage_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import asyncio

from v4_proto.dydxprotocol.clob.tx_pb2 import LeverageEntry

from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
from dydx_v4_client.network import TESTNET
from dydx_v4_client.node.client import NodeClient
from dydx_v4_client.node.market import Market
from dydx_v4_client.wallet import Wallet
from tests.conftest import DYDX_TEST_MNEMONIC_3, TEST_ADDRESS_3

# Constants for market configuration
MARKET_ID = "ENA-USD"


def print_leverage_response(leverage_response, node, title="Leverage"):
"""Helper function to print leverage response in a readable format."""
print(f"\n{title}:")
decoded = node.transcode_response(leverage_response)
print(f"Decoded response: {decoded}")

if hasattr(leverage_response, "clob_pair_leverage"):
leverage_list = leverage_response.clob_pair_leverage
if len(leverage_list) == 0:
print("No leverage settings found for this address/subaccount.")
return

print("Per-CLOB leverage entries:")
for entry in leverage_list:
imf_percent = entry.custom_imf_ppm / 10_000 # ppm to %
target_leverage = (
1_000_000 / entry.custom_imf_ppm if entry.custom_imf_ppm > 0 else 0
)

print(
f" - CLOB Pair ID: {entry.clob_pair_id} | "
f"Custom IMF: {entry.custom_imf_ppm} ppm ({imf_percent}%) | "
f"Target Leverage: {target_leverage:.2f}x"
)
print(entry)
else:
print(
"No 'clob_pair_leverage' field on response; raw decoded response printed above."
)


async def set_leverage_with_verification(
node: NodeClient,
wallet: Wallet,
address: str,
subaccount_number: int,
clob_pair_id: int,
leverage: int,
custom_imf_ppm: int,
) -> bool:
"""
Set leverage and verify it was set correctly.

Args:
leverage: The target leverage (e.g., 5 for 5x, 10 for 10x)
custom_imf_ppm: The custom IMF in parts per million

Returns:
bool: True if leverage was set successfully, False otherwise
"""
try:
print(f"\n{'=' * 60}")
print(
f"Setting leverage to {leverage}x ({custom_imf_ppm} ppm = {custom_imf_ppm / 10_000}% IMF) for CLOB pair {clob_pair_id}"
)
print(f"{'=' * 60}")

entry = LeverageEntry(clob_pair_id=clob_pair_id, custom_imf_ppm=custom_imf_ppm)
print(entry)
leverage_entries = [
entry,
]

response = await node.update_leverage(
wallet=wallet,
address=address,
subaccount_number=subaccount_number,
entries=leverage_entries,
)
print("Leverage update transaction submitted!")
print(f"Transaction response: {response}")

# Wait for the transaction to be processed
await asyncio.sleep(2)

# Verify leverage was set correctly
leverage_response = await node.get_leverage(address, subaccount_number)
print_leverage_response(
leverage_response, node, f"Leverage After Update ({leverage}x)"
)

return True

except Exception as e:
print(f"Error setting leverage to {leverage}x: {e}")
import traceback

traceback.print_exc()
return False


async def test():
"""Main function orchestrating the leverage collateral comparison test."""
market_id = MARKET_ID
print("=" * 60)
print(f"{market_id} Leverage Collateral Comparison Test")
print("=" * 60)

# Create the clients
node = await NodeClient.connect(TESTNET.node)
indexer = IndexerClient(TESTNET.rest_indexer)

# Create the wallet
wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC_3, TEST_ADDRESS_3)

subaccount_number = 0

# Get market data
market_data = await indexer.markets.get_perpetual_markets(market_id)
market = Market(market_data["markets"][market_id])

# Get the CLOB pair ID from the market data (dynamically retrieved, not hardcoded)
clob_pair_id = int(market.market["clobPairId"])
print(f"Using CLOB pair ID: {clob_pair_id} for market {market_id}")

success = await set_leverage_with_verification(
node, wallet, TEST_ADDRESS_3, subaccount_number, clob_pair_id, 5, 200_000
)
if not success:
print("Failed to set leverage to 5x. Aborting.")
return
success = await set_leverage_with_verification(
node, wallet, TEST_ADDRESS_3, subaccount_number, clob_pair_id, 10, 100_000
)
if not success:
print("Failed to set leverage to 10x. Aborting.")
return
print("Test complete!")
print("=" * 60)


if __name__ == "__main__":
asyncio.run(test())
Loading
Loading