Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c5f6797
feat: create and update docs dependency group in pyproject.toml
Karrenbelt Sep 18, 2025
65b737e
chore: remove empty files in docs
Karrenbelt Sep 18, 2025
67806d9
feat: update mkdocs.yml
Karrenbelt Sep 18, 2025
9a7605d
docs: index.md
Karrenbelt Sep 18, 2025
ccef9e6
docs: installation.md
Karrenbelt Sep 18, 2025
3630d0f
docs: account-model.md
Karrenbelt Sep 18, 2025
232df90
docs: authentication.md
Karrenbelt Sep 18, 2025
38fec84
docs: bridging.md
Karrenbelt Sep 18, 2025
defde63
docs: clients.md
Karrenbelt Sep 18, 2025
e7be616
docs: quickstart.md
Karrenbelt Sep 18, 2025
e8a06a2
Merge branch 'main' into docs/getting-started
Karrenbelt Sep 18, 2025
132dd84
docs: update env variable names
Karrenbelt Oct 1, 2025
1553bc9
docs: update bridging from idealised to current interface
Karrenbelt Oct 1, 2025
7982503
docs: reference create_new_pk.py
Karrenbelt Oct 1, 2025
e6e4c9b
Merge branch 'main' into docs/getting-started
Karrenbelt Oct 29, 2025
457ea04
Merge branch 'main' into docs/getting-started
Karrenbelt Oct 29, 2025
49d5262
feat: templates/transaction_struct.py
Karrenbelt Oct 30, 2025
73cc049
feat: inject_transaction_structs in generate-models
Karrenbelt Oct 30, 2025
ffb51f7
chore: generate-models
Karrenbelt Oct 30, 2025
a4076c6
feat: cli command group: transaction
Karrenbelt Oct 30, 2025
11d8300
refactor: DERIVE_SUBACCOUNT -> DERIVE_SUBACCOUNT_ID
Karrenbelt Oct 30, 2025
015e2d5
docs: update authentication, bridging and quickstart
Karrenbelt Oct 30, 2025
571810b
refactor: move properties up and private methods down on MarketOperat…
Karrenbelt Oct 30, 2025
7638cc9
fix: Makefile generate commands
Karrenbelt Oct 30, 2025
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
2 changes: 1 addition & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DERIVE_SESSION_KEY=
DERIVE_WALLET=
DERIVE_SUBACCOUNT=
DERIVE_SUBACCOUNT_ID=
DERIVE_ENV=PROD
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,18 @@ release:
.PHONY: generate-models
generate-models:
python scripts/generate-models.py
poetry run ruff format derive_client/data/generated/models.py
poetry run ruff check --fix derive_client/data/generated/models.py
poetry run ruff format derive_client/data/generated/models.py

.PHONY: generate-rest-api
generate-rest-api:
python scripts/generate-rest-api.py
poetry run ruff format derive_client/_clients/rest/
poetry run ruff check --fix derive_client/_clients/rest/
poetry run ruff format derive_client/_clients/rest/

.PHONY: generate-rest-async-http
generate-rest-async-http:
python scripts/generate-rest-async-http.py
poetry run ruff check --fix tests/test_clients/test_rest/test_async_http
poetry run ruff format tests/test_clients/test_rest/test_async_http
poetry run ruff check --fix tests/test_clients/test_rest/test_async_http
48 changes: 24 additions & 24 deletions derive_client/_clients/rest/async_http/markets.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ def __init__(self, *, public_api: AsyncPublicAPI, logger: Logger):
self._perp_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {}
self._option_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {}

@property
def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached ERC20 instruments."""

if not self._erc20_instruments_cache:
raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the erc20_instruments_cache.")
return self._erc20_instruments_cache

@property
def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached perpetual instruments."""

if not self._perp_instruments_cache:
raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the perp_instruments_cache.")
return self._perp_instruments_cache

@property
def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached option instruments."""

if not self._option_instruments_cache:
raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the option_instruments_cache.")
return self._option_instruments_cache

async def fetch_instruments(
self,
*,
Expand Down Expand Up @@ -108,30 +132,6 @@ def _get_cache_for_type(self, instrument_type: InstrumentType) -> dict[str, Inst
case _:
raise TypeError(f"Unsupported instrument_type: {instrument_type!r}")

@property
def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached ERC20 instruments."""

if not self._erc20_instruments_cache:
raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the erc20_instruments_cache.")
return self._erc20_instruments_cache

@property
def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached perpetual instruments."""

if not self._perp_instruments_cache:
raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the perp_instruments_cache.")
return self._perp_instruments_cache

@property
def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached option instruments."""

if not self._option_instruments_cache:
raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the option_instruments_cache.")
return self._option_instruments_cache

def _get_cached_instrument(self, *, instrument_name: str) -> InstrumentPublicResponseSchema:
"""Internal helper to retrieve an instrument from cache."""

Expand Down
48 changes: 24 additions & 24 deletions derive_client/_clients/rest/http/markets.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ def __init__(self, *, public_api: PublicAPI, logger: Logger):
self._perp_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {}
self._option_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {}

@property
def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached ERC20 instruments."""

if not self._erc20_instruments_cache:
self.fetch_instruments(instrument_type=InstrumentType.erc20)
return self._erc20_instruments_cache

@property
def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached perpetual instruments."""

if not self._perp_instruments_cache:
self.fetch_instruments(instrument_type=InstrumentType.perp)
return self._perp_instruments_cache

@property
def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached option instruments."""

if not self._option_instruments_cache:
self.fetch_instruments(instrument_type=InstrumentType.option)
return self._option_instruments_cache

def fetch_instruments(
self,
*,
Expand Down Expand Up @@ -108,30 +132,6 @@ def _get_cache_for_type(self, instrument_type: InstrumentType) -> dict[str, Inst
case _:
raise TypeError(f"Unsupported instrument_type: {instrument_type!r}")

@property
def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached ERC20 instruments."""

if not self._erc20_instruments_cache:
self.fetch_instruments(instrument_type=InstrumentType.erc20)
return self._erc20_instruments_cache

@property
def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached perpetual instruments."""

if not self._perp_instruments_cache:
self.fetch_instruments(instrument_type=InstrumentType.perp)
return self._perp_instruments_cache

@property
def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]:
"""Get cached option instruments."""

if not self._option_instruments_cache:
self.fetch_instruments(instrument_type=InstrumentType.option)
return self._option_instruments_cache

def _get_cached_instrument(self, *, instrument_name: str) -> InstrumentPublicResponseSchema:
"""Internal helper to retrieve an instrument from cache."""

Expand Down
4 changes: 3 additions & 1 deletion derive_client/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from ._mmp import mmp
from ._orders import order
from ._positions import position
from ._transactions import transaction

click.rich_click.USE_RICH_MARKUP = True

Expand All @@ -34,7 +35,7 @@
)
@click.pass_context
def cli(ctx, session_key_path: Path | None, env_file: Path | None):
"""Derive v2 client command line interface."""
"""Derive client command line interface."""

ctx.ensure_object(dict)
client = create_client(ctx=ctx, session_key_path=session_key_path, env_file=env_file)
Expand All @@ -47,3 +48,4 @@ def cli(ctx, session_key_path: Path | None, env_file: Path | None):
cli.add_command(mmp)
cli.add_command(order)
cli.add_command(position)
cli.add_command(transaction)
6 changes: 3 additions & 3 deletions derive_client/cli/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def create_client(

session_key = session_key_path.read_text().strip() if session_key_path else os.environ.get("DERIVE_SESSION_KEY")
wallet = os.environ.get("DERIVE_WALLET")
subaccount_id = os.environ.get("DERIVE_SUBACCOUNT")
subaccount_id = os.environ.get("DERIVE_SUBACCOUNT_ID")
env = Environment[os.environ.get("DERIVE_ENV", "PROD").upper()]

missing = []
Expand All @@ -33,7 +33,7 @@ def create_client(
if not wallet:
missing.append("DERIVE_WALLET: Not found in environment variables.")
if not subaccount_id:
missing.append("DERIVE_SUBACCOUNT: Not found in environment variables.")
missing.append("DERIVE_SUBACCOUNT_ID: Not found in environment variables.")

if missing:
error_msg = "Missing required configuration:\n\n" + "\n".join(f" • {m}" for m in missing)
Expand All @@ -45,7 +45,7 @@ def create_client(
error_msg += "\n\nProvide via environment variables or create a .env file with:"
error_msg += "\n DERIVE_SESSION_KEY=<your-session-key>"
error_msg += "\n DERIVE_WALLET=<your-wallet-address>"
error_msg += "\n DERIVE_SUBACCOUNT=<your-subaccount-id>"
error_msg += "\n DERIVE_SUBACCOUNT_ID=<your-subaccount-id>"
error_msg += "\n DERIVE_ENV=PROD # optional, defaults to PROD"

raise click.ClickException(error_msg)
Expand Down
93 changes: 93 additions & 0 deletions derive_client/cli/_transactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""CLI commands for transactions."""

from __future__ import annotations

from decimal import Decimal

import pandas as pd
import rich_click as click

from ._utils import struct_to_series


@click.group("transaction")
@click.pass_context
def transaction(ctx):
"""Move funds between wallet and subaccount."""


@transaction.command("get")
@click.argument(
"transaction_id",
required=True,
)
@click.pass_context
def get(ctx, transaction_id: str):
"""Used for getting a transaction by its transaction id."""

client = ctx.obj["client"]
subaccount = client.active_subaccount
transaction = subaccount.transactions.get(transaction_id=transaction_id)

tx_data = dict(
subaccount_id=transaction.data.subaccount_id,
status=transaction.status.name,
asset_name=transaction.data.asset_name,
amount=transaction.data.data.amount,
)
series = pd.Series(tx_data)

print("\n=== Transaction ===")
print(series.to_string(index=True))
if transaction.error_log:
print(f"\nError: {transaction.error_log.error}")


@transaction.command("deposit-to-subaccount")
@click.argument(
"amount",
type=Decimal,
required=True,
)
@click.argument(
"asset_name",
required=True,
)
@click.pass_context
def deposit_to_subaccount(ctx, amount: Decimal, asset_name: str):
"""Deposit an asset to a subaccount."""

client = ctx.obj["client"]
subaccount = client.active_subaccount
deposit = subaccount.transactions.deposit_to_subaccount(
amount=amount,
asset_name=asset_name,
)

print(f"\n=== Deposit to subaccount {subaccount.id} ===")
print(struct_to_series(deposit).to_string(index=True))


@transaction.command("withdraw-from-subaccount")
@click.argument(
"amount",
type=Decimal,
required=True,
)
@click.argument(
"asset_name",
required=True,
)
@click.pass_context
def withdraw_from_subaccount(ctx, amount: Decimal, asset_name: str):
"""Withdraw an asset to wallet."""

client = ctx.obj["client"]
subaccount = client.active_subaccount
withdrawal = subaccount.transactions.withdraw_from_subaccount(
amount=amount,
asset_name=asset_name,
)

print(f"\n=== Withdrawal from subaccount {subaccount.id} ===")
print(struct_to_series(withdrawal).to_string(index=True))
28 changes: 26 additions & 2 deletions derive_client/data/generated/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1318,10 +1318,34 @@ class PublicGetTransactionParamsSchema(Struct):
transaction_id: str


class TransactionDataInner(Struct):
asset: str
amount: str
decimals: int


class TransactionData(Struct):
data: TransactionDataInner
nonce: int
owner: str
expiry: int
module: str
signer: str
asset_id: str
signature: str
asset_name: str
subaccount_id: int
is_atomic_signing: bool


class TransactionErrorLog(Struct):
error: str


class PublicGetTransactionResultSchema(Struct):
data: dict
data: TransactionData
status: TxStatus
error_log: Optional[dict] = None
error_log: Optional[TransactionErrorLog] = None
transaction_hash: Optional[str] = None


Expand Down
27 changes: 27 additions & 0 deletions derive_client/data/templates/transaction_structs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Struct definitions for PublicGetTransactionResultSchema nested types."""

from msgspec import Struct


class TransactionDataInner(Struct):
asset: str
amount: str
decimals: int


class TransactionData(Struct):
data: TransactionDataInner
nonce: int
owner: str
expiry: int
module: str
signer: str
asset_id: str
signature: str
asset_name: str
subaccount_id: int
is_atomic_signing: bool


class TransactionErrorLog(Struct):
error: str
4 changes: 0 additions & 4 deletions docs/api/constants.md

This file was deleted.

4 changes: 0 additions & 4 deletions docs/api/enums.md

This file was deleted.

Loading
Loading