Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 allways/cli/dendrite_lite.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def get_ephemeral_wallet() -> bt.Wallet:
hotkey_file = Path(wallet_path) / EPHEMERAL_WALLET_NAME / 'hotkeys' / EPHEMERAL_HOTKEY_NAME
if not hotkey_file.exists():
hotkey_file.parent.mkdir(parents=True, exist_ok=True)
wallet.create_if_non_existent(coldkey_use_password=False, hotkey_use_password=False)
wallet.create_if_non_existent(coldkey_use_password=False, hotkey_use_password=False, suppress=True)
bt.logging.info('Created ephemeral wallet for dendrite-lite')

return wallet
Expand Down
18 changes: 11 additions & 7 deletions allways/cli/swap_commands/claim.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
"""alw claim - Claim a pending slash payout for a timed-out swap."""

import os

import click

from allways.classes import SwapStatus
from allways.cli.help import StyledCommand
from allways.cli.swap_commands.helpers import console, from_rao, get_cli_context, loading, print_contract_error
from allways.cli.swap_commands.view import DEFAULT_DASHBOARD_URL
from allways.cli.swap_commands.helpers import (
console,
dashboard_url,
from_rao,
get_cli_context,
loading,
print_contract_error,
)
from allways.contract_client import ContractError


Expand All @@ -24,7 +28,7 @@ def claim_command(swap_id: int, yes: bool):
[dim]Examples:
$ alw claim 42[/dim]
"""
_, wallet, _, client = get_cli_context()
config, wallet, _, client = get_cli_context()

console.print(f'\n[bold]Claim Slash — Swap #{swap_id}[/bold]\n')

Expand All @@ -47,12 +51,12 @@ def claim_command(swap_id: int, yes: bool):
)
return

dashboard_url = os.environ.get('ALLWAYS_DASHBOARD_URL', DEFAULT_DASHBOARD_URL).rstrip('/')
dashboard = dashboard_url(config.get('network'))
console.print(
f'[yellow]Nothing to claim for swap #{swap_id}.[/yellow]\n'
'[dim]The slash was either paid directly to the user at timeout, already claimed,\n'
'or the swap never timed out. Only the original swap user can claim a pending slash.\n'
f'Refund history:[/dim] {dashboard_url}/swap/{swap_id}\n'
f'Refund history:[/dim] {dashboard}/swap/{swap_id}\n'
)
return

Expand Down
19 changes: 19 additions & 0 deletions allways/cli/swap_commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,25 @@ def is_local_network(network: str) -> bool:
return any(host in network for host in ('127.0.0.1', 'localhost', '0.0.0.0'))


PROD_DASHBOARD_URL = 'https://all-ways.io'
TEST_DASHBOARD_URL = 'https://test.all-ways.io'


def dashboard_url(network: Optional[str] = None) -> str:
"""Resolve the dashboard base URL for the active network.

finney maps to the mainnet dashboard; every other network (test, local,
custom endpoints) maps to the testnet dashboard. ALLWAYS_DASHBOARD_URL
overrides everything for staging/local use.
"""
override = os.environ.get('ALLWAYS_DASHBOARD_URL')
if override:
return override.rstrip('/')
if network is None:
network = get_effective_config().get('network', 'finney')
return (PROD_DASHBOARD_URL if network == 'finney' else TEST_DASHBOARD_URL).rstrip('/')


def to_rao(amount_tao: float) -> int:
"""Convert TAO to rao."""
return int(amount_tao * TAO_TO_RAO)
Expand Down
68 changes: 35 additions & 33 deletions allways/cli/swap_commands/swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
blocks_to_minutes_str,
clear_pending_swap,
console,
dashboard_url,
find_matching_miners,
from_rao,
get_cli_context,
Expand Down Expand Up @@ -740,33 +741,29 @@ def swap_now_command(
return

# Show send capability for the source chain (skip in non-interactive mode).
# Only asked once we know a miner can actually fill this swap.
if not skip_confirm:
if from_chain == 'tao':
console.print('\n [green]TAO will be sent automatically from your wallet.[/green]')
# Only asked once we know a miner can actually fill this swap. The TAO
# auto-send notice is deferred to the unlock step, nearer the password prompt.
if not skip_confirm and from_chain != 'tao':
# Prefix-check the value (not just truthy): dotenv can leak an inline comment as the var.
wif_raw = (os.environ.get('BTC_PRIVATE_KEY') or '').strip()
has_private_key = bool(wif_raw) and wif_raw[0] in '5KLc9'
btc_mode = os.environ.get('BTC_MODE', 'lightweight')
is_local = is_local_network(config.get('network', 'finney'))

if has_private_key and not is_local:
console.print('\n [green]BTC_PRIVATE_KEY set — will sign and attempt BTC sends locally.[/green]')
else:
# Prefix-check the value (not just truthy): dotenv can leak an inline comment as the var.
wif_raw = (os.environ.get('BTC_PRIVATE_KEY') or '').strip()
has_private_key = bool(wif_raw) and wif_raw[0] in '5KLc9'
btc_mode = os.environ.get('BTC_MODE', 'lightweight')
is_local = is_local_network(config.get('network', 'finney'))

if has_private_key and not is_local:
console.print('\n [green]BTC_PRIVATE_KEY set — will sign and attempt BTC sends locally.[/green]')
else:
# External signing path — covers both the lightweight/no-key
# case and any other environment without automatic BTC sending.
# Taproot caveat only applies to the BYO-sig flow.
taproot_note = (
' Taproot (bc1p…) unsupported.' if btc_mode == 'lightweight' and not has_private_key else ''
)
console.print(
f'\n [yellow]BTC signing & sending are external — you will sign at reserve/confirm'
f' and run [cyan]alw swap post-tx <tx_hash>[/cyan] after broadcasting.{taproot_note}[/yellow]'
)
if not click.confirm(' Continue?', default=True):
console.print('[yellow]Cancelled[/yellow]')
return
# External signing path — covers both the lightweight/no-key
# case and any other environment without automatic BTC sending.
# Taproot caveat only applies to the BYO-sig flow.
taproot_note = ' Taproot (bc1p…) unsupported.' if btc_mode == 'lightweight' and not has_private_key else ''
console.print(
f'\n [yellow]BTC signing & sending are external — you will sign at reserve/confirm'
f' and run [cyan]alw swap post-tx <tx_hash>[/cyan] after broadcasting.{taproot_note}[/yellow]'
)
if not click.confirm(' Continue?', default=True):
console.print('[yellow]Cancelled[/yellow]')
return

# Rate is TAO/BTC: highest is best when receiving TAO, lowest when sending TAO.
canon_from, canon_to = canonical_pair(from_chain, to_chain)
Expand Down Expand Up @@ -989,8 +986,13 @@ def swap_now_command(
console.print('[yellow]Cancelled[/yellow]')
return

# Unlock coldkey once (password prompt) — all subsequent signing uses the cached key
# Unlock coldkey (password prompt) to sign the reservation proof.
if from_chain == 'tao':
console.print(f"\n [green]TAO sends automatically from your wallet '[bold]{wallet.name}[/bold]'.[/green]")
console.print(
' [dim]Password = your Bittensor coldkey password. This first unlock signs the reservation '
'proof (proving you own the source address).[/dim]'
)
from_key = wallet.coldkey
else:
from_key = None
Expand Down Expand Up @@ -1072,9 +1074,7 @@ def swap_now_command(
save_pending_swap(state)

if request_hash:
from allways.cli.swap_commands.view import DEFAULT_DASHBOARD_URL

dashboard = os.environ.get('ALLWAYS_DASHBOARD_URL', DEFAULT_DASHBOARD_URL).rstrip('/')
dashboard = dashboard_url(config.get('network'))
console.print(f' [dim]Reservation:[/dim] [cyan]{dashboard}/reservations/{request_hash}[/cyan]')

# Step 9: Send funds (or use pre-provided tx hash)
Expand Down Expand Up @@ -1106,6 +1106,9 @@ def swap_now_command(

from_tx_hash = None
if from_chain == 'tao':
console.print(
' [dim]Enter your coldkey password again — this unlock signs and broadcasts the TAO transfer.[/dim]'
)
for attempt in range(2):
send_result = send_tao_transfer(wallet, subtensor, deposit_address, from_amount)
if send_result is not None:
Expand Down Expand Up @@ -1218,13 +1221,12 @@ def swap_now_command(
return

# Watch swap through lifecycle
from allways.cli.swap_commands.view import DEFAULT_DASHBOARD_URL, watch_swap
from allways.cli.swap_commands.view import watch_swap

final_swap = watch_swap(client, swap_id)

# Show completion receipt / timeout notice
if final_swap and final_swap.status == SwapStatus.COMPLETED:
display_receipt(final_swap)
elif final_swap and final_swap.status == SwapStatus.TIMED_OUT:
dashboard_url = os.environ.get('ALLWAYS_DASHBOARD_URL', DEFAULT_DASHBOARD_URL).rstrip('/')
display_timeout_notice(final_swap, dashboard_url)
display_timeout_notice(final_swap, dashboard_url(config.get('network')))
6 changes: 2 additions & 4 deletions allways/cli/swap_commands/view.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""alw view - View swaps, miners, and rates."""

import os
import time
from dataclasses import replace

Expand All @@ -19,6 +18,7 @@
blocks_to_minutes_str,
clear_pending_swap,
console,
dashboard_url,
from_rao,
get_cli_context,
hydrate_pending_swap,
Expand All @@ -36,11 +36,9 @@
)
from allways.contract_client import ContractError

DEFAULT_DASHBOARD_URL = 'https://test.all-ways.io'


def _dashboard_url() -> str:
return os.environ.get('ALLWAYS_DASHBOARD_URL', DEFAULT_DASHBOARD_URL).rstrip('/')
return dashboard_url()


@click.group('view', cls=StyledGroup)
Expand Down
Loading