Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cbb0660
Problem: Ledger wallet users cannot use Aleph to send transactions.
Sep 10, 2025
4e8c9b6
Fix: Solve code quality issues.
Sep 10, 2025
f4c0f2a
Fix: Solve issue loading the good configuration.
Sep 10, 2025
c52f9dc
Fix: Solved definitively the wallet selection issue and also solved a…
Sep 10, 2025
b994c79
Fix: Solved code-quality issue.
Sep 10, 2025
cb798b5
Feature: cli load_account to handle account selections
1yam Oct 31, 2025
9c347b5
Fix: using load_account instead of _load_account
1yam Oct 31, 2025
f770d0f
fix: unit test mock
1yam Oct 31, 2025
677799d
Feature: aleph account init to create based config for new user using…
1yam Oct 31, 2025
725e042
fix: aleph account list now handle ledger device
1yam Oct 31, 2025
051b0e4
fix: aleph account address now handle ledger device
1yam Oct 31, 2025
55308d6
fix: aleph account export-private-key handle ledger case (can't expor…
1yam Oct 31, 2025
d433012
Feature: missing unit test for ledger
1yam Nov 3, 2025
def2144
Fix: handle common error using ledger (OsError / LedgerError)
1yam Nov 4, 2025
46bbbdf
Fix: handle change from account on sdk side
1yam Nov 7, 2025
96ee9cd
Fix: remove init commands and ensure that config file/folder and subf…
1yam Nov 7, 2025
edd5f07
Fix: AccountLike renamed to AccountTypes
1yam Nov 7, 2025
ffc634e
fix: AlephAccount should bez AccountTypes
1yam Nov 7, 2025
5e66ce2
fix: account init commands unit test should be removed since not usef…
1yam Nov 7, 2025
681b59d
Feature: utils functions for ledger
1yam Nov 19, 2025
47e5128
Fix: ensure ledger is connected before loading ledger account
1yam Nov 19, 2025
41b7da6
Fix: avoid connecting to ledger when not needed
1yam Nov 19, 2025
3bdc3b3
Fix: use BaseEthAccount instead of EthAccount in instance create and …
1yam Nov 19, 2025
9116552
fix: refactor aleph account configure and list to handle ledger
1yam Nov 19, 2025
f884163
Fix: call_program_crn_list can now filter node when fetching if they …
1yam Nov 19, 2025
8db0817
Fix: unit test
1yam Nov 19, 2025
c79a795
Feature: --no args for aleph account configure
1yam Nov 19, 2025
703cb47
fix: linting issue
1yam Nov 19, 2025
2a41932
Feature: load acount unit test
1yam Nov 19, 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
13 changes: 8 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,20 @@ dependencies = [
"aiodns==3.2",
"aiohttp==3.11.13",
"aleph-message>=1.0.5",
"aleph-sdk-python>=2.1",
"base58==2.1.1", # Needed now as default with _load_account changement
#"aleph-sdk-python>=2.1",
"aleph-sdk-python @ git+https://github.com/aleph-im/aleph-sdk-python@andres-feature-implement_ledger_wallet",
"base58==2.1.1", # Needed now as default with _load_account changement
"click<8.2",
"py-sr25519-bindings==0.2", # Needed for DOT signatures
"ledgerblue>=0.1.48",
"ledgereth>=0.10",
"py-sr25519-bindings==0.2", # Needed for DOT signatures
"pydantic>=2",
"pygments==2.19.1",
"pynacl==1.5", # Needed now as default with _load_account changement
"pynacl==1.5", # Needed now as default with _load_account changement
"python-magic==0.4.27",
"rich==13.9.*",
"setuptools>=65.5",
"substrate-interface==1.7.11", # Needed for DOT signatures
"substrate-interface==1.7.11", # Needed for DOT signatures
"textual==0.73",
"typer==0.15.2",
]
Expand Down
302 changes: 248 additions & 54 deletions src/aleph_client/commands/account.py

Large diffs are not rendered by default.

58 changes: 41 additions & 17 deletions src/aleph_client/commands/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

import typer
from aiohttp import ClientResponseError, ClientSession
from aleph.sdk.account import _load_account
from aleph.sdk.client import AuthenticatedAlephHttpClient
from aleph.sdk.conf import settings
from aleph.sdk.types import AccountFromPrivateKey
from aleph.sdk.client import AlephHttpClient, AuthenticatedAlephHttpClient
from aleph.sdk.conf import AccountType, load_main_configuration, settings
from aleph.sdk.utils import extended_json_encoder
from aleph_message.models import Chain, MessageType
from aleph_message.status import MessageStatus
Expand All @@ -21,7 +19,7 @@

from aleph_client.commands import help_strings
from aleph_client.commands.utils import setup_logging
from aleph_client.utils import AsyncTyper, sanitize_url
from aleph_client.utils import AccountTypes, AsyncTyper, load_account, sanitize_url

logger = logging.getLogger(__name__)
app = AsyncTyper(no_args_is_help=True)
Expand Down Expand Up @@ -59,7 +57,7 @@ async def forget(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)
address = account.get_address() if address is None else address

if key == "security" and not is_same_context():
Expand Down Expand Up @@ -132,7 +130,7 @@ async def post(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)
address = account.get_address() if address is None else address

if key == "security" and not is_same_context():
Expand Down Expand Up @@ -194,10 +192,19 @@ async def get(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
address = account.get_address() if address is None else address
config_file_path = Path(settings.CONFIG_FILE)
config = load_main_configuration(config_file_path)
account_type = config.type if config else None

async with AuthenticatedAlephHttpClient(account=account, api_server=settings.API_HOST) as client:
# Avoid connecting to ledger
if not account_type or account_type == AccountType.IMPORTED:
account = load_account(private_key, private_key_file)
if account and not address:
address = account.get_address()
elif not address and config and config.address:
address = config.address

async with AlephHttpClient(api_server=settings.API_HOST) as client:
aggregates = None
try:
aggregates = await client.fetch_aggregate(address=address, key=key)
Expand Down Expand Up @@ -229,9 +236,17 @@ async def list_aggregates(
"""Display all aggregates associated to an account"""

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
address = account.get_address() if address is None else address
config_file_path = Path(settings.CONFIG_FILE)
config = load_main_configuration(config_file_path)
account_type = config.type if config else None

# Avoid connecting to ledger
if not account_type or account_type == AccountType.IMPORTED:
account = load_account(private_key, private_key_file)
if account and not address:
address = account.get_address()
elif not address and config and config.address:
address = config.address

aggr_link = f"{sanitize_url(settings.API_HOST)}/api/v0/aggregates/{address}.json"
async with ClientSession() as session:
Expand Down Expand Up @@ -304,7 +319,7 @@ async def authorize(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

data = await get(
key="security",
Expand Down Expand Up @@ -378,7 +393,7 @@ async def revoke(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

data = await get(
key="security",
Expand Down Expand Up @@ -433,8 +448,17 @@ async def permissions(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
address = account.get_address() if address is None else address
config_file_path = Path(settings.CONFIG_FILE)
config = load_main_configuration(config_file_path)
account_type = config.type if config else None

# Avoid connecting to ledger
if not account_type or account_type == AccountType.IMPORTED:
account = load_account(private_key, private_key_file)
if account and not address:
address = account.get_address()
elif not address and config and config.address:
address = config.address

data = await get(
key="security",
Expand Down
34 changes: 23 additions & 11 deletions src/aleph_client/commands/credit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import typer
from aiohttp import ClientResponseError
from aleph.sdk import AlephHttpClient
from aleph.sdk.account import _load_account
from aleph.sdk.conf import settings
from aleph.sdk.types import AccountFromPrivateKey
from aleph.sdk.conf import AccountType, load_main_configuration, settings
from aleph.sdk.utils import displayable_amount
from rich import box
from rich.console import Console
Expand All @@ -17,7 +15,7 @@

from aleph_client.commands import help_strings
from aleph_client.commands.utils import setup_logging
from aleph_client.utils import AsyncTyper
from aleph_client.utils import AsyncTyper, load_account

logger = logging.getLogger(__name__)
app = AsyncTyper(no_args_is_help=True)
Expand All @@ -41,10 +39,17 @@ async def show(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
config_file_path = Path(settings.CONFIG_FILE)
config = load_main_configuration(config_file_path)
account_type = config.type if config else None

if account and not address:
address = account.get_address()
# Avoid connecting to ledger
if not account_type or account_type == AccountType.IMPORTED:
account = load_account(private_key, private_key_file)
if account and not address:
address = account.get_address()
elif not address and config and config.address:
address = config.address

if address:
async with AlephHttpClient(api_server=settings.API_HOST) as client:
Expand Down Expand Up @@ -87,10 +92,17 @@ async def history(
):
setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)

if account and not address:
address = account.get_address()
config_file_path = Path(settings.CONFIG_FILE)
config = load_main_configuration(config_file_path)
account_type = config.type if config else None

# Avoid connecting to ledger
if not account_type or account_type == AccountType.IMPORTED:
account = load_account(private_key, private_key_file)
if account and not address:
address = account.get_address()
elif not address and config and config.address:
address = config.address

try:
# Comment the original API call for testing
Expand Down
16 changes: 7 additions & 9 deletions src/aleph_client/commands/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from typing import Annotated, Optional, cast

import typer
from aleph.sdk.account import _load_account
from aleph.sdk.client import AlephHttpClient, AuthenticatedAlephHttpClient
from aleph.sdk.conf import settings
from aleph.sdk.domain import (
Expand All @@ -18,7 +17,6 @@
)
from aleph.sdk.exceptions import DomainConfigurationError
from aleph.sdk.query.filters import MessageFilter
from aleph.sdk.types import AccountFromPrivateKey
from aleph_message.models import AggregateMessage
from aleph_message.models.base import MessageType
from rich.console import Console
Expand All @@ -27,7 +25,7 @@

from aleph_client.commands import help_strings
from aleph_client.commands.utils import is_environment_interactive
from aleph_client.utils import AsyncTyper
from aleph_client.utils import AccountTypes, AsyncTyper, load_account

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -65,7 +63,7 @@ async def check_domain_records(fqdn, target, owner):


async def attach_resource(
account: AccountFromPrivateKey,
account,
fqdn: Hostname,
item_hash: Optional[str] = None,
catch_all_path: Optional[str] = None,
Expand Down Expand Up @@ -137,7 +135,7 @@ async def attach_resource(
)


async def detach_resource(account: AccountFromPrivateKey, fqdn: Hostname, interactive: Optional[bool] = None):
async def detach_resource(account: AccountTypes, fqdn: Hostname, interactive: Optional[bool] = None):
domain_info = await get_aggregate_domain_info(account, fqdn)
interactive = is_environment_interactive() if interactive is None else interactive

Expand Down Expand Up @@ -187,7 +185,7 @@ async def add(
] = settings.PRIVATE_KEY_FILE,
):
"""Add and link a Custom Domain."""
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)
interactive = False if (not ask) else is_environment_interactive()

console = Console()
Expand Down Expand Up @@ -272,7 +270,7 @@ async def attach(
] = settings.PRIVATE_KEY_FILE,
):
"""Attach resource to a Custom Domain."""
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

await attach_resource(
account,
Expand All @@ -294,7 +292,7 @@ async def detach(
] = settings.PRIVATE_KEY_FILE,
):
"""Unlink Custom Domain."""
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

await detach_resource(account, Hostname(fqdn), interactive=False if (not ask) else None)
raise typer.Exit()
Expand All @@ -309,7 +307,7 @@ async def info(
] = settings.PRIVATE_KEY_FILE,
):
"""Show Custom Domain Details."""
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

console = Console()
domain_validator = DomainValidator()
Expand Down
28 changes: 17 additions & 11 deletions src/aleph_client/commands/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
import typer
from aiohttp import ClientResponseError
from aleph.sdk import AlephHttpClient, AuthenticatedAlephHttpClient
from aleph.sdk.account import _load_account
from aleph.sdk.conf import settings
from aleph.sdk.types import AccountFromPrivateKey, StorageEnum, StoredContent
from aleph.sdk.conf import AccountType, load_main_configuration, settings
from aleph.sdk.types import StorageEnum, StoredContent
from aleph.sdk.utils import safe_getattr
from aleph_message.models import ItemHash, StoreMessage
from aleph_message.status import MessageStatus
Expand All @@ -23,7 +22,7 @@

from aleph_client.commands import help_strings
from aleph_client.commands.utils import setup_logging
from aleph_client.utils import AsyncTyper
from aleph_client.utils import AccountTypes, AsyncTyper, load_account

logger = logging.getLogger(__name__)
app = AsyncTyper(no_args_is_help=True)
Expand All @@ -44,7 +43,7 @@ async def pin(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

async with AuthenticatedAlephHttpClient(account=account, api_server=settings.API_HOST) as client:
result: StoreMessage
Expand Down Expand Up @@ -75,7 +74,7 @@ async def upload(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

async with AuthenticatedAlephHttpClient(account=account, api_server=settings.API_HOST) as client:
if not path.is_file():
Expand Down Expand Up @@ -181,7 +180,7 @@ async def forget(

setup_logging(debug)

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)
account: AccountTypes = load_account(private_key, private_key_file)

hashes = [ItemHash(item_hash) for item_hash in item_hash.split(",")]

Expand Down Expand Up @@ -270,10 +269,17 @@ async def list_files(
json: Annotated[bool, typer.Option(help="Print as json instead of rich table")] = False,
):
"""List all files for a given address"""
account: AccountFromPrivateKey = _load_account(private_key, private_key_file)

if account and not address:
address = account.get_address()
config_file_path = Path(settings.CONFIG_FILE)
config = load_main_configuration(config_file_path)
account_type = config.type if config else None

# Avoid connecting to ledger
if not account_type or account_type == AccountType.IMPORTED:
account = load_account(private_key, private_key_file)
if account and not address:
address = account.get_address()
elif not address and config and config.address:
address = config.address

if address:
# Build the query parameters
Expand Down
Loading
Loading