Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 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
2 changes: 1 addition & 1 deletion v4-client-py-v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,4 @@ poetry run pytest

Built by Nethermind: [@piwonskp](https://github.com/piwonskp), [@samtin0x](https://github.com/samtin0x), [@therustmonk](https://github.com/therustmonk)

For more details about the grant see [link](https://www.dydxgrants.com/grants/python-trading-client).
For more details about the grant see [link](https://www.dydxgrants.com/grants/python-trading-client).
4 changes: 2 additions & 2 deletions v4-client-py-v2/documentation/getting_price_quotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ asyncio.run(get_all_markets())

### Getting a Specific Market's Data

To get data for a specific market (e.g., BTC-USD):
To get data for a specific market (e.g., ENA-USD):
```python
MARKET_BTC_USD = "BTC-USD"
MARKET_BTC_USD = "ENA-USD"

async def get_specific_market():
response = await client.markets.get_perpetual_markets(market=MARKET_BTC_USD)
Expand Down
6 changes: 3 additions & 3 deletions v4-client-py-v2/documentation/using_testnet_faucet.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ from dydx_v4_client.faucet_client import FaucetClient
from dydx_v4_client.network import TESTNET_FAUCET

# Replace this with your actual testnet address
TEST_ADDRESS = "your_testnet_address_here"
TEST_ADDRESS_3 = "your_testnet_address_here"

async def get_tokens_from_faucet():
faucet = FaucetClient(TESTNET_FAUCET)

# Get non-native tokens (USDC)
response_non_native = await faucet.fill(TEST_ADDRESS, 0, 2000)
response_non_native = await faucet.fill(TEST_ADDRESS_3, 0, 2000)

# Get native tokens (DV4TNT)
response_native = await faucet.fill_native(TEST_ADDRESS)
response_native = await faucet.fill_native(TEST_ADDRESS_3)

print("Non-native token response:", response_non_native)
print("Native token response:", response_native)
Expand Down
12 changes: 6 additions & 6 deletions v4-client-py-v2/documentation/using_websockets.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ from dydx_v4_client.indexer.socket.websocket import IndexerSocket, CandlesResolu
from dydx_v4_client.network import TESTNET

# Replace with your actual address
TEST_ADDRESS = "your_address_here"
ETH_USD = "ETH-USD"
TEST_ADDRESS_3 = "your_address_here"
ETH_USD = "ENA-USD"
```


Expand All @@ -30,17 +30,17 @@ async def simple_subscription():
# Subscribe to markets
await ws.markets.subscribe()

# Subscribe to orderbook for ETH-USD
# Subscribe to orderbook for ENA-USD
await ws.order_book.subscribe(ETH_USD)

# Subscribe to trades for ETH-USD
# Subscribe to trades for ENA-USD
await ws.trades.subscribe(ETH_USD)

# Subscribe to 15-minute candles for ETH-USD
# Subscribe to 15-minute candles for ENA-USD
await ws.candles.subscribe(ETH_USD, CandlesResolution.FIFTEEN_MINUTES)

# Subscribe to a specific subaccount
await ws.subaccounts.subscribe(TEST_ADDRESS, 0)
await ws.subaccounts.subscribe(TEST_ADDRESS_3, 0)

# Keep the connection alive
while True:
Expand Down
2 changes: 1 addition & 1 deletion v4-client-py-v2/dydx_v4_client/indexer/socket/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def process(self, message):

Maybe allow creating standalone channels? Ie.:
order_book = OrderBook()
order_book.subscribe(id="BTC-USD")
order_book.subscribe(id="ENA-USD")
"""
raise NotImplementedError()

Expand Down
6 changes: 3 additions & 3 deletions v4-client-py-v2/dydx_v4_client/node/balance_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
from dydx_v4_client.network import TESTNET
from dydx_v4_client.node.client import NodeClient
from dydx_v4_client.node.fee import Denom
from tests.conftest import TEST_ADDRESS
from tests.conftest import TEST_ADDRESS_3


async def check_funding_account_balance(
test_address: str, min_balance_usdc: int = 21000000
test_address: str, min_balance_usdc: int = 100000
) -> bool:
"""
Check if the funding account has sufficient balance.
Expand Down Expand Up @@ -70,7 +70,7 @@ async def check_funding_account_balance(
async def main():
"""Main function for CLI usage."""
# Use the test address from conftest.py
default_test_address = TEST_ADDRESS
default_test_address = TEST_ADDRESS_3

# Allow override via command line argument
test_address = sys.argv[1] if len(sys.argv) > 1 else default_test_address
Expand Down
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 @@ -273,3 +277,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,
)
4 changes: 2 additions & 2 deletions v4-client-py-v2/examples/account_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from dydx_v4_client.indexer.rest.constants import TradingRewardAggregationPeriod
from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
from dydx_v4_client.network import TESTNET
from tests.conftest import TEST_ADDRESS
from tests.conftest import TEST_ADDRESS_3


async def test_account():
indexer = IndexerClient(TESTNET.rest_indexer)
test_address = TEST_ADDRESS
test_address = TEST_ADDRESS_3

try:
response = await indexer.account.get_subaccounts(test_address)
Expand Down
4 changes: 2 additions & 2 deletions v4-client-py-v2/examples/affiliate_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
from dydx_v4_client.network import TESTNET
from tests.conftest import TEST_ADDRESS
from tests.conftest import TEST_ADDRESS_3


async def test_affiliate():
indexer = IndexerClient(TESTNET.rest_indexer)
test_address = TEST_ADDRESS
test_address = TEST_ADDRESS_3

try:
response = await indexer.affiliate.get_metadata(test_address)
Expand Down
10 changes: 5 additions & 5 deletions v4-client-py-v2/examples/authenticator_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
from dydx_v4_client.node.market import Market
from dydx_v4_client.wallet import Wallet
from tests.conftest import (
DYDX_TEST_MNEMONIC,
DYDX_TEST_MNEMONIC_3,
DYDX_TEST_MNEMONIC_2,
TEST_ADDRESS,
TEST_ADDRESS_3,
TEST_ADDRESS_2,
)


MARKET_ID = "ETH-USD"
MARKET_ID = "ENA-USD"


async def authenticator_example():
Expand All @@ -30,8 +30,8 @@ async def authenticator_example():
# Set up the primary wallet for which we want to add authenticator
wallet = await Wallet.from_mnemonic(
node,
mnemonic=DYDX_TEST_MNEMONIC,
address=TEST_ADDRESS,
mnemonic=DYDX_TEST_MNEMONIC_3,
address=TEST_ADDRESS_3,
)

# Set up the "trader" wallet
Expand Down
2 changes: 1 addition & 1 deletion v4-client-py-v2/examples/basic_adder.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ async def place_order(self, side: OrderSide, size: float, px: Decimal):
address=self.address,
subaccount_number=self.subaccount_number,
client_id=0,
clob_pair_id=0,
clob_pair_id=127,
order_flags=0,
)
time_in_force = (
Expand Down
10 changes: 5 additions & 5 deletions v4-client-py-v2/examples/batch_cancel_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
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, TEST_ADDRESS
from tests.conftest import DYDX_TEST_MNEMONIC_3, TEST_ADDRESS_3
from v4_proto.dydxprotocol.clob.tx_pb2 import OrderBatch
from v4_proto.dydxprotocol.subaccounts.subaccount_pb2 import SubaccountId


MARKET_ID = "BTC-USD"
MARKET_ID = "ENA-USD"
PERPETUAL_PAIR_BTC_USD = 0


Expand All @@ -26,14 +26,14 @@ async def test_batch_cancel():
market = Market(
(await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID]
)
wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC_3, TEST_ADDRESS_3)

# Place multiple orders
orders = []
client_ids = []
for _ in range(3):
client_id = random.randint(0, MAX_CLIENT_ID)
order_id = market.order_id(TEST_ADDRESS, 0, client_id, OrderFlags.SHORT_TERM)
order_id = market.order_id(TEST_ADDRESS_3, 0, client_id, OrderFlags.SHORT_TERM)
client_ids.append(client_id)
current_block = await node.latest_block_height()
order = market.order(
Expand All @@ -55,7 +55,7 @@ async def test_batch_cancel():
wallet.sequence += 1

# Prepare batch cancel
subaccount_id = SubaccountId(owner=TEST_ADDRESS, number=0)
subaccount_id = SubaccountId(owner=TEST_ADDRESS_3, number=0)
order_batch = OrderBatch(clob_pair_id=PERPETUAL_PAIR_BTC_USD, client_ids=client_ids)
cancellation_current_block = await node.latest_block_height()

Expand Down
14 changes: 7 additions & 7 deletions v4-client-py-v2/examples/builder_code_parameter_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
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, TEST_ADDRESS
from tests.conftest import DYDX_TEST_MNEMONIC_3, TEST_ADDRESS_3

MARKET_ID = "ETH-USD"
MARKET_ID = "ENA-USD"


async def place_market_order_with_builder_code(size: float):
Expand All @@ -22,10 +22,10 @@ async def place_market_order_with_builder_code(size: float):
market = Market(
(await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID]
)
wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC_3, TEST_ADDRESS_3)

order_id = market.order_id(
TEST_ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM
TEST_ADDRESS_3, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM
)

current_block = await node.latest_block_height()
Expand All @@ -39,7 +39,7 @@ async def place_market_order_with_builder_code(size: float):
time_in_force=Order.TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
reduce_only=False,
good_til_block=current_block + 10,
builder_address=TEST_ADDRESS,
builder_address=TEST_ADDRESS_3,
fee_ppm=500,
)

Expand All @@ -54,10 +54,10 @@ async def place_market_order_with_builder_code(size: float):
await asyncio.sleep(5)

fills = await indexer.account.get_subaccount_fills(
address=TEST_ADDRESS, subaccount_number=0, ticker=MARKET_ID, limit=1
address=TEST_ADDRESS_3, subaccount_number=0, ticker=MARKET_ID, limit=1
)
print(f"Fills: {fills}")
assert fills["fills"][0]["builderAddress"] == TEST_ADDRESS
assert fills["fills"][0]["builderAddress"] == TEST_ADDRESS_3


asyncio.run(place_market_order_with_builder_code(0.00001))
Loading
Loading